Move video stretching into video player

This commit is contained in:
Ylva Selling
2023-02-16 12:04:13 -05:00
parent 8d79e348b3
commit 7154b6c4fa
7 changed files with 173 additions and 168 deletions
+20
View File
@@ -39,6 +39,11 @@
namespace openspace {
enum class PlaybackMode {
MapToSimulationTime = 0,
RealTimeLoop
};
class VideoPlayer : public properties::PropertyOwner {
public:
VideoPlayer(const ghoul::Dictionary& dictionary);
@@ -59,6 +64,7 @@ public:
double videoDuration() const;
double fps() const;
double currentPlaybackTime() const;
PlaybackMode playbackMode() const;
void reset();
void destroy();
@@ -71,6 +77,20 @@ public:
properties::TriggerProperty _reset;
properties::BoolProperty _playAudio;
private:
PlaybackMode _playbackMode = PlaybackMode::RealTimeLoop; // Default is to loop
// Map to simulation time functions
double correctVideoPlaybackTime() const;
bool isWithingStartEndTime() const;
void updateFrameDuration();
void syncToSimulationTime();
// Video stretching: map to simulation time animation mode
double _startJ200Time = 0.0;
double _endJ200Time = 0.0;
double _timeAtLastRender = 0.0;
double _frameDuration = 0.0;
// libmpv property keys
enum class LibmpvPropertyKey : uint64_t {
-22
View File
@@ -56,38 +56,16 @@ public:
Tile tile(const TileIndex& tileIndex) override final;
Tile::Status tileStatus(const TileIndex& tileIndex) override final;
TileDepthTransform depthTransform() override final;
void syncToSimulationTime();
static documentation::Documentation Documentation();
private:
enum class PlaybackMode {
MapToSimulationTime = 0,
RealTimeLoop
};
// Map to simulation time functions
double correctVideoPlaybackTime() const;
bool isWithingStartEndTime() const;
void updateFrameDuration();
void internalInitialize() override final;
void internalDeinitialize() override final;
PlaybackMode _playbackMode = PlaybackMode::RealTimeLoop; // Default is to loop
// Video stretching: map to simulation time animation mode
double _startJ200Time = 0.0;
double _endJ200Time = 0.0;
double _timeAtLastRender = 0.0;
double _frameDuration = 0.0;
double _fps = 24.0;
double _videoDuration = 1.0;
// Tile handling
std::map<TileIndex::TileHashKey, Tile> _tileCache; // Cache for rendering 1 frame
bool _tileIsReady = false;
double _seekThreshold = 1.0; // Threshold to ensure we seek to a different time
VideoPlayer _videoPlayer;
};
+7 -3
View File
@@ -59,11 +59,15 @@ RenderableVideoSphere::RenderableVideoSphere(const ghoul::Dictionary& dictionary
: RenderableSphere(dictionary)
, _videoPlayer(dictionary)
{
addProperty(_videoPlayer._play);
addProperty(_videoPlayer._pause);
addProperty(_videoPlayer._goToStart);
addProperty(_videoPlayer._reset);
addProperty(_videoPlayer._playAudio);
if (_videoPlayer.playbackMode() == PlaybackMode::RealTimeLoop) {
// Video interaction. Only valid for real time looping
addProperty(_videoPlayer._play);
addProperty(_videoPlayer._pause);
addProperty(_videoPlayer._goToStart);
}
}
void RenderableVideoSphere::bindTexture() {
+7 -3
View File
@@ -66,11 +66,15 @@ ScreenSpaceVideo::ScreenSpaceVideo(const ghoul::Dictionary& dictionary)
identifier = makeUniqueIdentifier(identifier);
setIdentifier(identifier);
addProperty(_videoPlayer._play);
addProperty(_videoPlayer._pause);
addProperty(_videoPlayer._goToStart);
addProperty(_videoPlayer._reset);
addProperty(_videoPlayer._playAudio);
if (_videoPlayer.playbackMode() == PlaybackMode::RealTimeLoop) {
// Video interaction. Only valid for real time looping
addProperty(_videoPlayer._play);
addProperty(_videoPlayer._pause);
addProperty(_videoPlayer._goToStart);
}
}
void ScreenSpaceVideo::update() {
+137
View File
@@ -72,12 +72,48 @@ namespace {
"Play audio"
};
constexpr openspace::properties::Property::PropertyInfo StartTimeInfo = {
"StartTime",
"Start Time",
"The date and time that the video should start in the format "
"'YYYY MM DD hh:mm:ss'."
};
constexpr openspace::properties::Property::PropertyInfo EndTimeInfo = {
"EndTime",
"End Time",
"The date and time that the video should end in the format "
"'YYYY MM DD hh:mm:ss'."
};
constexpr openspace::properties::Property::PropertyInfo PlaybackModeInfo = {
"PlaybackMode",
"Playback Mode",
"Determines the way the video should be played. The start and end time of the "
"video can be set, or the video can be played as a loop in real time."
};
struct [[codegen::Dictionary(VideoPlayer)]] Parameters {
// [[codegen::verbatim(VideoInfo.description)]]
std::string video;
// [[codegen::verbatim(AudioInfo.description)]]
std::optional<bool> playAudio;
// [[codegen::verbatim(StartTimeInfo.description)]]
std::optional<std::string> startTime [[codegen::datetime()]];
// [[codegen::verbatim(EndTimeInfo.description)]]
std::optional<std::string> endTime [[codegen::datetime()]];
enum class PlaybackMode {
MapToSimulationTime = 0,
RealTimeLoop
};
// The mode of how the video should be played back.
// Default is video is played back according to the set start and end times.
std::optional<PlaybackMode> playbackMode;
};
#include "videoplayer_codegen.cpp"
} // namespace
@@ -173,6 +209,41 @@ VideoPlayer::VideoPlayer(const ghoul::Dictionary& dictionary)
_goToStart.onChange([this]() { goToStart(); });
_reset.onChange([this]() { reset(); });
_playAudio.onChange([this]() { toggleMute(); });
if (p.playbackMode.has_value()) {
switch (*p.playbackMode) {
case Parameters::PlaybackMode::RealTimeLoop:
_playbackMode = PlaybackMode::RealTimeLoop;
break;
case Parameters::PlaybackMode::MapToSimulationTime:
_playbackMode = PlaybackMode::MapToSimulationTime;
break;
default:
LERROR("Missing playback mode in VideoTileProvider");
throw ghoul::MissingCaseException();
}
}
if (_playbackMode == PlaybackMode::MapToSimulationTime) {
if (!p.startTime.has_value() || !p.endTime.has_value()) {
LERROR("Video tile layer tried to map to simulation time but lacked start or"
" end time");
return;
}
_startJ200Time = Time::convertTime(*p.startTime);
_endJ200Time = Time::convertTime(*p.endTime);
ghoul_assert(_endJ200Time > _startJ200Time, "Invalid times for video");
// Change the video time if OpenSpace time changes
global::timeManager->addTimeJumpCallback([this]() {
seekToTime(correctVideoPlaybackTime());
});
// Ensure we are synchronized to OpenSpace time in presync step
global::callback::preSync->emplace_back([this]() {
syncToSimulationTime();
});
}
global::callback::postSyncPreDraw->emplace_back([this]() {
if (_isDestroying) {
@@ -556,6 +627,9 @@ void VideoPlayer::handleMpvProperties(mpv_event* event) {
}
_videoDuration = *duration;
if (_playbackMode == PlaybackMode::MapToSimulationTime) {
updateFrameDuration();
}
LINFO(fmt::format("Duration: {}", *duration));
break;
@@ -740,6 +814,9 @@ void VideoPlayer::handleMpvProperties(mpv_event* event) {
break;
}
_fps = *fps;
if (_playbackMode == PlaybackMode::MapToSimulationTime) {
updateFrameDuration();
}
LINFO(fmt::format("Detected fps: {}", *fps));
_seekThreshold = 2.0 * (1.0 / _fps);
@@ -811,6 +888,66 @@ double VideoPlayer::currentPlaybackTime() const {
return _currentVideoTime;
}
PlaybackMode VideoPlayer::playbackMode() const {
return _playbackMode;
}
bool VideoPlayer::isWithingStartEndTime() const {
const double now = global::timeManager->time().j2000Seconds();
return now <= _endJ200Time && now >= _startJ200Time;
}
void VideoPlayer::updateFrameDuration() {
double openspaceVideoLength = (_endJ200Time - _startJ200Time) / _videoDuration;
_frameDuration = (1.0 / _fps) * openspaceVideoLength;
}
double VideoPlayer::correctVideoPlaybackTime() const {
const double now = global::timeManager->time().j2000Seconds();
double percentage = 0.0;
if (now > _endJ200Time) {
percentage = 1.0;
}
else if (now < _startJ200Time) {
percentage = 0.0;
}
else {
percentage = (now - _startJ200Time) / (_endJ200Time - _startJ200Time);
}
return percentage * videoDuration();
}
void VideoPlayer::syncToSimulationTime() {
if (_playbackMode == PlaybackMode::MapToSimulationTime) {
// If we are in valid times, step frames accordingly
if (isWithingStartEndTime()) {
double now = global::timeManager->time().j2000Seconds();
double deltaTime = now - _timeAtLastRender;
if (deltaTime > _frameDuration) {
// Stepping forwards
stepFrameForward();
_timeAtLastRender = now;
}
else if (deltaTime < -_frameDuration) {
// Stepping backwards
stepFrameBackward();
_timeAtLastRender = now;
}
}
else if (!_isPaused) {
pause();
}
// Make sure we are at the correct time
double time = correctVideoPlaybackTime();
bool shouldSeek = abs(time - _currentVideoTime) > _seekThreshold;
if (shouldSeek) {
seekToTime(time);
}
}
}
void VideoPlayer::createFBO(int width, int height) {
LINFO(fmt::format("Creating new FBO with width: {} and height: {}", width, height));
+1 -139
View File
@@ -38,42 +38,7 @@
namespace {
constexpr std::string_view _loggerCat = "VideoTileProvider";
constexpr openspace::properties::Property::PropertyInfo StartTimeInfo = {
"StartTime",
"Start Time",
"The date and time that the video should start in the format "
"'YYYY MM DD hh:mm:ss'."
};
constexpr openspace::properties::Property::PropertyInfo EndTimeInfo = {
"EndTime",
"End Time",
"The date and time that the video should end in the format "
"'YYYY MM DD hh:mm:ss'."
};
constexpr openspace::properties::Property::PropertyInfo PlaybackModeInfo = {
"PlaybackMode",
"Playback Mode",
"Determines the way the video should be played. The start and end time of the "
"video can be set, or the video can be played as a loop in real time."
};
struct [[codegen::Dictionary(VideoTileProvider)]] Parameters {
// [[codegen::verbatim(StartTimeInfo.description)]]
std::optional<std::string> startTime [[codegen::datetime()]];
// [[codegen::verbatim(EndTimeInfo.description)]]
std::optional<std::string> endTime [[codegen::datetime()]];
enum class PlaybackMode {
MapToSimulationTime = 0,
RealTimeLoop
};
// The mode of how the video should be played back.
// Default is video is played back according to the set start and end times.
std::optional<PlaybackMode> playbackMode;
};
#include "videotileprovider_codegen.cpp"
@@ -99,45 +64,12 @@ VideoTileProvider::VideoTileProvider(const ghoul::Dictionary& dictionary)
addProperty(_videoPlayer._reset);
addProperty(_videoPlayer._playAudio);
if (p.playbackMode.has_value()) {
switch (*p.playbackMode) {
case Parameters::PlaybackMode::RealTimeLoop:
_playbackMode = PlaybackMode::RealTimeLoop;
break;
case Parameters::PlaybackMode::MapToSimulationTime:
_playbackMode = PlaybackMode::MapToSimulationTime;
break;
default:
LERROR("Missing playback mode in VideoTileProvider");
throw ghoul::MissingCaseException();
}
}
if (_playbackMode == PlaybackMode::RealTimeLoop) {
if (_videoPlayer.playbackMode() == PlaybackMode::RealTimeLoop) {
// Video interaction. Only valid for real time looping
addProperty(_videoPlayer._play);
addProperty(_videoPlayer._pause);
addProperty(_videoPlayer._goToStart);
}
else if (_playbackMode == PlaybackMode::MapToSimulationTime) {
if (!p.startTime.has_value() || !p.endTime.has_value()) {
LERROR("Video tile layer tried to map to simulation time but lacked start or"
" end time");
return;
}
_startJ200Time = Time::convertTime(*p.startTime);
_endJ200Time = Time::convertTime(*p.endTime);
ghoul_assert(_endJ200Time > _startJ200Time, "Invalid times for video");
// Change the video time if OpenSpace time changes
global::timeManager->addTimeJumpCallback([this]() {
_videoPlayer.seekToTime(correctVideoPlaybackTime());
});
// Ensure we are synchronized to OpenSpace time in presync step
global::callback::preSync->emplace_back([this]() {
syncToSimulationTime();
});
}
}
Tile VideoTileProvider::tile(const TileIndex& tileIndex) {
@@ -165,7 +97,6 @@ Tile VideoTileProvider::tile(const TileIndex& tileIndex) {
Tile::Status::OK
};
}
return _tileCache[hash];
}
@@ -208,75 +139,6 @@ ChunkTile VideoTileProvider::chunkTile(TileIndex tileIndex, int parents, int max
return traverseTree(tileIndex, parents, maxParents, ascendToParent, uvTransform);
}
bool VideoTileProvider::isWithingStartEndTime() const {
const double now = global::timeManager->time().j2000Seconds();
return now <= _endJ200Time && now >= _startJ200Time;
}
void VideoTileProvider::updateFrameDuration() {
double openspaceVideoLength = (_endJ200Time - _startJ200Time) / _videoDuration;
_frameDuration = (1.0 / _fps) * openspaceVideoLength;
}
double VideoTileProvider::correctVideoPlaybackTime() const {
const double now = global::timeManager->time().j2000Seconds();
double percentage = 0.0;
if (now > _endJ200Time) {
percentage = 1.0;
}
else if (now < _startJ200Time) {
percentage = 0.0;
}
else {
percentage = (now - _startJ200Time) / (_endJ200Time - _startJ200Time);
}
return percentage * _videoPlayer.videoDuration();
}
void VideoTileProvider::syncToSimulationTime() {
if (_playbackMode == PlaybackMode::MapToSimulationTime) {
bool fpsChanged = isDifferent(_videoPlayer.fps(), _fps);
bool durationChanged = isDifferent(_videoPlayer.videoDuration(), _videoDuration);
if (fpsChanged) {
_fps = _videoPlayer.fps();
}
if (durationChanged) {
_videoDuration = _videoPlayer.videoDuration();
}
if (fpsChanged || durationChanged) {
updateFrameDuration();
}
if (!_videoPlayer.isPaused()) {
//_videoPlayer.pause();
}
// If we are in valid times, step frames accordingly
if (isWithingStartEndTime()) {
double now = global::timeManager->time().j2000Seconds();
double deltaTime = now - _timeAtLastRender;
if (deltaTime > _frameDuration) {
// Stepping forwards
_videoPlayer.stepFrameForward();
_timeAtLastRender = now;
}
else if (deltaTime < -_frameDuration) {
// Stepping backwards
_videoPlayer.stepFrameBackward();
_timeAtLastRender = now;
}
}
else if (!_videoPlayer.isPaused()) {
_videoPlayer.pause();
}
// Make sure we are at the correct time
double time = correctVideoPlaybackTime();
bool shouldSeek = abs(time - _videoPlayer.currentPlaybackTime()) > _seekThreshold;
if (shouldSeek) {
_videoPlayer.seekToTime(time);
}
}
}
int VideoTileProvider::minLevel() {
return 1;
}
+1 -1
View File
@@ -1,6 +1,6 @@
local layer = {
Identifier = "SosLoopTest",
File = "C:/Users/ylvaselling/Documents/Work/Testmovies/chlorophyll_model_2048.mp4",
Video = "C:/Users/ylvaselling/Documents/Work/Testmovies/chlorophyll_model_2048.mp4",
Name = "Science On A Sphere Loop Video",
Enabled = asset.enabled,
Type = "VideoTileLayer",