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) { 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())) { 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<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false)); Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
@ -690,6 +700,17 @@ bool MovieElement::canAutoPlay() const {
return _visible && !_paused; 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) { void MovieElement::render(Window *window) {
if (_needsReset) { if (_needsReset) {
_videoDecoder->setReverse(_reversed); _videoDecoder->setReverse(_reversed);
@ -731,7 +752,8 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
if (_videoDecoder->isPaused()) if (_videoDecoder->isPaused())
_videoDecoder->pauseVideo(false); _videoDecoder->pauseVideo(false);
_currentPlayState = kMediaStatePlaying; if (_currentPlayState != kMediaStatePlayingLastFrame)
_currentPlayState = kMediaStatePlaying;
checkContinuously = true; checkContinuously = true;
} }
} else { } else {
@ -747,25 +769,25 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
uint32 targetTS = _currentTimestamp; uint32 targetTS = _currentTimestamp;
int framesDecodedThisFrame = 0; 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) { 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()) if (_videoDecoder->endOfVideo())
targetTS = _reversed ? _playRange.min : _playRange.max; targetTS = _reversed ? _playRange.min : _playRange.max;
else else
@ -782,10 +804,14 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
// Sync TS to the end of video if we hit the end // Sync TS to the end of video if we hit the end
bool triggerEndEvents = false;
if (_currentPlayState == kMediaStatePlayingLastFrame)
triggerEndEvents = true;
if (targetTS != _currentTimestamp) { if (targetTS != _currentTimestamp) {
assert(!_paused); assert(!_paused);
// Check media cues // Check media cues
for (MediaCueState *mediaCue : _mediaCues) for (MediaCueState *mediaCue : _mediaCues)
mediaCue->checkTimestampChange(runtime, _currentTimestamp, targetTS, checkContinuously, true); mediaCue->checkTimestampChange(runtime, _currentTimestamp, targetTS, checkContinuously, true);
@ -793,33 +819,38 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
_currentTimestamp = targetTS; _currentTimestamp = targetTS;
if (_currentTimestamp == maxTS) { if (_currentTimestamp == maxTS) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtLastCel, 0), DynamicValue(), getSelfReference())); if (maxTS == _maxTimestamp) {
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false)); // If this play range plays through to the end, then delay end events 1 frame so it has a chance to render
runtime->queueMessage(dispatch); _currentPlayState = kMediaStatePlayingLastFrame;
} } else
triggerEndEvents = true;
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 (_currentPlayState == kMediaStatePlaying && _videoDecoder->endOfVideo()) { if (triggerEndEvents) {
if (_alternate) { if (!_loop) {
_reversed = !_reversed; _paused = true;
if (!_videoDecoder->setReverse(_reversed)) {
warning("Failed to reverse video decoder, disabling it"); Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPause, 0), DynamicValue(), getSelfReference()));
_videoDecoder.reset(); 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; _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(); _videoDecoder->stop();
_currentPlayState = kMediaStateStopped; _currentPlayState = kMediaStateStopped;
_needsReset = true; _needsReset = true;
_contentsDirty = true;
_shouldPlayIfNotPaused = true; _shouldPlayIfNotPaused = true;
_paused = false; _paused = false;

View File

@ -44,6 +44,7 @@ struct MToonMetadata;
enum MediaState { enum MediaState {
kMediaStatePlaying, kMediaStatePlaying,
kMediaStatePlayingLastFrame,
kMediaStateStopped, kMediaStateStopped,
kMediaStatePaused, kMediaStatePaused,
}; };
@ -87,6 +88,7 @@ public:
void deactivate() override; void deactivate() override;
bool canAutoPlay() const override; bool canAutoPlay() const override;
void queueAutoPlayEvents(Runtime *runtime, bool isAutoPlaying) override;
void render(Window *window) override; void render(Window *window) override;
void playMedia(Runtime *runtime, Project *project) 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 // 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. // 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 // The event does, however, need to be sent immediately.
// property is set from script... need to verify and update this comment.)
if (!thread->getRuntime()->isAwaitingSceneTransition()) { if (!thread->getRuntime()->isAwaitingSceneTransition()) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(targetValue ? EventIDs::kPause : EventIDs::kUnpause, 0), DynamicValue(), getSelfReference())); 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)); Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
@ -6435,6 +6434,14 @@ bool Element::canAutoPlay() const {
return false; 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 { bool Element::isElement() const {
return true; return true;
} }
@ -6462,11 +6469,7 @@ void Element::triggerAutoPlay(Runtime *runtime) {
_haveCheckedAutoPlay = true; _haveCheckedAutoPlay = true;
if (canAutoPlay()) { queueAutoPlayEvents(runtime, 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);
}
} }
bool Element::resolveMediaMarkerLabel(const Label& label, int32 &outResolution) const { bool Element::resolveMediaMarkerLabel(const Label& label, int32 &outResolution) const {

View File

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