MTROPOLIS: Add movie loops

This commit is contained in:
elasota 2022-06-22 00:32:02 -04:00
parent 508610ad22
commit 562feebd3c
4 changed files with 88 additions and 49 deletions

View File

@ -603,8 +603,18 @@ MiniscriptInstructionOutcome MovieElement::writeRefAttribute(MiniscriptThread *t
}
VThreadState MovieElement::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
// The reaction to the Play command should be to fire Unpaused and then fire Played.
// At First Cel is NOT fired by Play commands for some reason.
if (Event::create(EventIDs::kPlay, 0).respondsTo(msg->getEvent())) {
// These reverse order
if (_paused)
{
_paused = false;
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kUnpause, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->sendMessageOnVThread(dispatch);
}
{
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
@ -690,6 +700,17 @@ bool MovieElement::canAutoPlay() const {
return _visible && !_paused;
}
void MovieElement::queueAutoPlayEvents(Runtime *runtime, bool isAutoPlaying) {
// At First Cel event fires even if the movie isn't playing, and it fires before Played
if (_visible) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtFirstCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
}
VisualElement::queueAutoPlayEvents(runtime, isAutoPlaying);
}
void MovieElement::render(Window *window) {
if (_needsReset) {
_videoDecoder->setReverse(_reversed);
@ -731,7 +752,8 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
if (_videoDecoder->isPaused())
_videoDecoder->pauseVideo(false);
_currentPlayState = kMediaStatePlaying;
if (_currentPlayState != kMediaStatePlayingLastFrame)
_currentPlayState = kMediaStatePlaying;
checkContinuously = true;
}
} else {
@ -747,25 +769,25 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
uint32 targetTS = _currentTimestamp;
int framesDecodedThisFrame = 0;
while (_videoDecoder->needsUpdate()) {
if (_playEveryFrame && framesDecodedThisFrame > 0)
break;
const Graphics::Surface *decodedFrame = _videoDecoder->decodeNextFrame();
// GNARLY HACK: QuickTimeDecoder doesn't return true for endOfVideo or false for needsUpdate until it
// tries decoding past the end, so we're assuming that the decoded frame memory stays valid until we
// actually have a new frame and continuing to use it.
if (decodedFrame) {
_contentsDirty = true;
framesDecodedThisFrame++;
_displayFrame = decodedFrame;
if (_playEveryFrame)
break;
}
}
if (_currentPlayState == kMediaStatePlaying) {
while (_videoDecoder->needsUpdate()) {
if (_playEveryFrame && framesDecodedThisFrame > 0)
break;
const Graphics::Surface *decodedFrame = _videoDecoder->decodeNextFrame();
// QuickTimeDecoder doesn't return true for endOfVideo or false for needsUpdate until it
// tries decoding past the end, so we're assuming that the decoded frame memory stays valid until we
// actually have a new frame and continuing to use it.
if (decodedFrame) {
_contentsDirty = true;
framesDecodedThisFrame++;
_displayFrame = decodedFrame;
if (_playEveryFrame)
break;
}
}
if (_videoDecoder->endOfVideo())
targetTS = _reversed ? _playRange.min : _playRange.max;
else
@ -782,10 +804,14 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
// Sync TS to the end of video if we hit the end
bool triggerEndEvents = false;
if (_currentPlayState == kMediaStatePlayingLastFrame)
triggerEndEvents = true;
if (targetTS != _currentTimestamp) {
assert(!_paused);
// Check media cues
for (MediaCueState *mediaCue : _mediaCues)
mediaCue->checkTimestampChange(runtime, _currentTimestamp, targetTS, checkContinuously, true);
@ -793,33 +819,38 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
_currentTimestamp = targetTS;
if (_currentTimestamp == maxTS) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtLastCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
}
if (_currentTimestamp == minTS) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtFirstCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
if (maxTS == _maxTimestamp) {
// If this play range plays through to the end, then delay end events 1 frame so it has a chance to render
_currentPlayState = kMediaStatePlayingLastFrame;
} else
triggerEndEvents = true;
}
}
if (_currentPlayState == kMediaStatePlaying && _videoDecoder->endOfVideo()) {
if (_alternate) {
_reversed = !_reversed;
if (!_videoDecoder->setReverse(_reversed)) {
warning("Failed to reverse video decoder, disabling it");
_videoDecoder.reset();
}
if (triggerEndEvents) {
if (!_loop) {
_paused = true;
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPause, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
uint32 endTS = _reversed ? _playRange.min : _playRange.max;
_videoDecoder->setEndTime(Audio::Timestamp(0, _timeScale).addFrames(endTS));
} else {
// It doesn't look like movies fire any events upon reaching the end, just At Last Cel and At First Cel
_videoDecoder->stop();
_currentPlayState = kMediaStateStopped;
}
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtLastCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
// For some reason, At First Cel isn't fired for movies, even when they loop or are set to timevalue 0
_videoDecoder->stop();
_currentPlayState = kMediaStateStopped;
if (_loop) {
_needsReset = true;
_currentTimestamp = _reversed ? _playRange.max : _playRange.min;
_contentsDirty = true;
}
}
}
}
@ -951,6 +982,7 @@ VThreadState MovieElement::startPlayingTask(const StartPlayingTaskData &taskData
_videoDecoder->stop();
_currentPlayState = kMediaStateStopped;
_needsReset = true;
_contentsDirty = true;
_shouldPlayIfNotPaused = true;
_paused = false;

View File

@ -44,6 +44,7 @@ struct MToonMetadata;
enum MediaState {
kMediaStatePlaying,
kMediaStatePlayingLastFrame,
kMediaStateStopped,
kMediaStatePaused,
};
@ -87,6 +88,7 @@ public:
void deactivate() override;
bool canAutoPlay() const override;
void queueAutoPlayEvents(Runtime *runtime, bool isAutoPlaying) override;
void render(Window *window) override;
void playMedia(Runtime *runtime, Project *project) override;

View File

@ -2963,8 +2963,7 @@ MiniscriptInstructionOutcome Structural::scriptSetPaused(MiniscriptThread *threa
// while at the Bureau light carousel, since the lever isn't flagged as paused but is set paused
// via an init script, and the lever trigger is detected via the pause event.
//
// (It's possible that this is actually yet another case of the event simply not being sent when the
// property is set from script... need to verify and update this comment.)
// The event does, however, need to be sent immediately.
if (!thread->getRuntime()->isAwaitingSceneTransition()) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(targetValue ? EventIDs::kPause : EventIDs::kUnpause, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
@ -6435,6 +6434,14 @@ bool Element::canAutoPlay() const {
return false;
}
void Element::queueAutoPlayEvents(Runtime *runtime, bool isAutoPlaying) {
if (isAutoPlaying) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, false, true));
runtime->queueMessage(dispatch);
}
}
bool Element::isElement() const {
return true;
}
@ -6462,11 +6469,7 @@ void Element::triggerAutoPlay(Runtime *runtime) {
_haveCheckedAutoPlay = true;
if (canAutoPlay()) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, false, true));
runtime->queueMessage(dispatch);
}
queueAutoPlayEvents(runtime, canAutoPlay());
}
bool Element::resolveMediaMarkerLabel(const Label& label, int32 &outResolution) const {

View File

@ -2263,6 +2263,8 @@ public:
virtual bool isVisual() const = 0;
virtual bool canAutoPlay() const;
virtual void queueAutoPlayEvents(Runtime *runtime, bool isAutoPlaying);
bool isElement() const override;
uint32 getStreamLocator() const;