Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions client_generic/Client/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1354,10 +1354,25 @@ bool CPlayer::SetPlaylistAtDream(const std::string& playlistUUID, const std::str
int64_t seekFrame;
seekFrame = (int64_t)g_Settings()->Get(
"settings.content.last_played_frame", uint64_t{});
// Guard against corrupted/wrapped values (e.g. uint32_t wrap from a previous VAAPI PTS bug).
// Any negative value or value implying > 24 hours of footage at 60fps is treated as invalid.
constexpr int64_t kMaxReasonableFrame = 24LL * 3600 * 60; // 24h @ 60fps
if (seekFrame < 0 || seekFrame > kMaxReasonableFrame) seekFrame = 0;

// Clamp the resume position into the dream's valid range. A saved
// last_played_frame may be negative, stale, hand-edited, or past the clip's
// real end (the frame index can reach or overrun the end at high playback
// speeds). A negative seek or one well past EOF hangs the decoder — it decodes
// no frames and never starts — so bound it to [0, frames-1]. Landing on the
// last frame yields a single frame and the clip finishes immediately; the
// streaming fallback in preflightNextDream then advances to the next dream.
// The decoder's own frame count is unreliable for streamed clips, so use the
// metadata count.
const int64_t dreamFrames = optionalDream->dream ? (int64_t)optionalDream->dream->frames : 0;
if (seekFrame < 0) {
seekFrame = 0;
} else if (dreamFrames > 0 && seekFrame >= dreamFrames) {
g_Log->Warning("Resume frame %lld past end of %s (length %lld); clamping to last frame",
(long long)seekFrame, optionalDream->dream->uuid.c_str(),
(long long)dreamFrames);
seekFrame = dreamFrames - 1;
}

// If we've reached here, the playlist is set and positioned at the correct dream
// Now we can start playing this dream
Expand Down
20 changes: 17 additions & 3 deletions client_generic/ContentDownloader/PlaylistManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -863,9 +863,23 @@ std::optional<PlaylistManager::NextDreamDecision> PlaylistManager::preflightNext
}
}

// No cached dreams available at all
g_Log->Warning("Preflight : no cached dreams available and canStream=false");
return std::nullopt;
// No cached dreams available at all. Returning nothing here makes the caller
// re-preflight every frame (a busy-loop) with no way to advance. Since the
// cache is empty, streaming the next dream is the only way to make progress,
// so fall back to it even though this is a canStream=false (prefer-cached)
// request. Cached content was preferred above; this only fires as a last
// resort (e.g. a fresh install with an exhausted download quota).
size_t streamPos = (m_currentPosition + 1) % m_playlist.size();
const auto& streamEntry = m_playlist[streamPos];
g_Log->Warning("Preflight : no cached dreams available; streaming next dream at position %zu", streamPos);
decision = {
streamPos,
TransitionType::StandardCrossfade,
m_cacheManager.getDream(streamEntry.uuid),
streamEntry.startKeyframe,
streamEntry.endKeyframe
};
return decision;
}

const auto& firstEntry = m_playlist[0];
Expand Down
17 changes: 16 additions & 1 deletion client_generic/TupleStorage/JSONStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,22 @@ bool JSONStorage::GetOrSetValue(
}
}
}
_targetValue = callback(currentValue);
// The callback extracts the value via boost::json as_*() accessors,
// which throw if the stored value isn't the expected kind — e.g. a
// hand-edited or out-of-range number (an integer too large for
// int64/uint64 is parsed as a double). Treat any such mismatch as
// "not found" so the caller falls back to its default instead of
// letting the exception escape and terminate the process.
try
{
_targetValue = callback(currentValue);
}
catch (const std::exception& e)
{
g_Log->Warning("JSONStorage: value for '%s' has unexpected type (%s); using default",
std::string(_entry).c_str(), e.what());
return false;
}
}
else
{
Expand Down