Files
2026-02-02 04:50:13 +01:00

197 lines
7.2 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/keymap.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "engines/util.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#if defined(USE_MPEG2) && defined(USE_A52)
#include "video/mpegps_decoder.h"
#endif
#include "zvision/zvision.h"
#include "zvision/core/clock.h"
#include "zvision/file/file_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/sound/volume_manager.h"
#include "zvision/text/subtitle_manager.h"
#include "zvision/video/rlf_decoder.h"
#include "zvision/video/zork_avi_decoder.h"
namespace ZVision {
Video::VideoDecoder *ZVision::loadAnimation(const Common::Path &fileName) {
debugC(5, kDebugVideo, "loadAnimation()");
Common::String tmpFileName = fileName.baseName();
tmpFileName.toLowercase();
Video::VideoDecoder *animation = NULL;
debugC(1, kDebugVideo, "Loading animation %s", fileName.toString().c_str());
if (tmpFileName.hasSuffix(".rlf"))
animation = new RLFDecoder();
else if (tmpFileName.hasSuffix(".avi"))
animation = new ZorkAVIDecoder();
#if defined(USE_MPEG2) && defined(USE_A52)
else if (tmpFileName.hasSuffix(".vob")) {
double amplification = getVolumeManager()->getVobAmplification(tmpFileName);
animation = new Video::MPEGPSDecoder(amplification);
}
#endif
else
error("Unknown suffix for animation %s", fileName.toString().c_str());
Common::File *file = getFileManager()->open(fileName);
if (!file)
error("Error opening %s", fileName.toString().c_str());
bool loaded = animation->loadStream(file);
if (!loaded)
error("Error loading animation %s", fileName.toString().c_str());
debugC(5, kDebugVideo, "~loadAnimation()");
return animation;
}
/**
* Play video at specified location.
*
* Pauses clock & normal game loop for duration of video; will still update & render subtitles & cursor.
*
* @param vid Source video
* @param dstRect Rectangle to play video into, defined relative to working window origin; video will scale to rectangle automatically.
* @param skippable Allow video to be skipped
* @param sub Subtitle associated with video
* @param srcRect Rectangle within video frame, defined relative to frame origin, to blit to output. Only used for removing baked-in letterboxing in ZGI DVD HD videos
*/
void ZVision::playVideo(Video::VideoDecoder &vid, Common::Rect dstRect, bool skippable, uint16 sub, Common::Rect srcRect) {
Common::Rect frameArea = Common::Rect(vid.getWidth(), vid.getHeight());
Common::Rect workingArea = _renderManager->getWorkingArea();
// If dstRect is empty, no specific scaling was requested. However, we may choose to do scaling anyway
bool scaled = false;
workingArea.moveTo(0, 0); // Set local origin system in this scope to origin of working area
debugC(1, kDebugVideo, "Playing video, source %d,%d,%d,%d, at destination %d,%d,%d,%d", srcRect.left, srcRect.top, srcRect.right, srcRect.bottom, dstRect.left, dstRect.top, dstRect.right, dstRect.bottom);
if (dstRect.isEmpty())
dstRect = frameArea;
dstRect.clip(workingArea);
debugC(2, kDebugVideo, "Clipped dstRect = %d,%d,%d,%d", dstRect.left, dstRect.top, dstRect.right, dstRect.bottom);
if (srcRect.isEmpty())
srcRect = frameArea;
else
srcRect.clip(frameArea);
debugC(2, kDebugVideo, "Clipped srcRect = %d,%d,%d,%d", srcRect.left, srcRect.top, srcRect.right, srcRect.bottom);
Graphics::ManagedSurface &outSurface = _renderManager->getVidSurface(dstRect);
dstRect.moveTo(0, 0);
dstRect.clip(Common::Rect(outSurface.w, outSurface.h));
debugC(2, kDebugVideo, "dstRect clipped with outSurface = %d,%d,%d,%d", dstRect.left, dstRect.top, dstRect.right, dstRect.bottom);
debugC(1, kDebugVideo, "Final size %d x %d, at working window coordinates %d, %d", srcRect.width(), srcRect.height(), dstRect.left, dstRect.top);
if (srcRect.width() != dstRect.width() || srcRect.height() != dstRect.height()) {
debugC(1, kDebugVideo, "Video will be scaled from %dx%d to %dx%d", srcRect.width(), srcRect.height(), dstRect.width(), dstRect.height());
scaled = true;
}
bool showSubs = (_scriptManager->getStateValue(StateKey_Subtitles) == 1);
_clock.stop();
vid.start();
_videoIsPlaying = true;
_cutscenesKeymap->setEnabled(true);
_gameKeymap->setEnabled(false);
// Only continue while the video is still playing
while (!shouldQuit() && !vid.endOfVideo() && vid.isPlaying()) {
// Check for engine quit and video stop key presses
while (_eventMan->pollEvent(_event)) {
switch (_event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
switch ((ZVisionAction)_event.customType) {
case kZVisionActionQuit:
if (ConfMan.hasKey("confirm_exit") && ConfMan.getBool("confirm_exit")) {
if (quit(true, true))
vid.stop();
}
else {
quit(false);
vid.stop();
}
break;
case kZVisionActionSkipCutscene:
if (skippable) {
vid.stop();
}
break;
default:
break;
}
default:
break;
}
}
if (vid.needsUpdate()) {
const Graphics::Surface *frame = vid.decodeNextFrame();
if (showSubs && sub > 0)
_subtitleManager->update(vid.getCurFrame(), sub);
if (frame) {
_renderManager->renderSceneToScreen(true, true, true); // Redraw text area to clean background of subtitles for videos that don't fill entire working area, e.g, Nemesis sarcophagi
if (scaled) {
debugC(8, kDebugVideo, "Scaled blit from area %d x %d to video output surface at output surface position %d, %d", srcRect.width(), srcRect.height(), dstRect.left, dstRect.top);
outSurface.blitFrom(*frame, srcRect, dstRect);
} else {
debugC(8, kDebugVideo, "Simple blit from area %d x %d to video output surface at output surface position %d, %d", srcRect.width(), srcRect.height(), dstRect.left, dstRect.top);
outSurface.simpleBlitFrom(*frame, srcRect, dstRect.origin());
}
_subtitleManager->process(0);
}
}
// Always update the screen so the mouse continues to render & video does not skip
_renderManager->renderSceneToScreen(true, true, false);
_system->delayMillis(vid.getTimeToNextFrame() / 2); // Exponentially decaying delay
}
vid.close(); // Ensure resources are freed.
_cutscenesKeymap->setEnabled(false);
_gameKeymap->setEnabled(true);
_videoIsPlaying = false;
_clock.start();
debugC(1, kDebugVideo, "Video playback complete");
}
} // End of namespace ZVision