- cleanup of movie player code, migration to event interface.

This commit is contained in:
Christoph Oelckers 2021-04-16 00:02:09 +02:00
parent dbd3e1de44
commit aad6158288
3 changed files with 188 additions and 66 deletions

View file

@ -51,13 +51,22 @@
#include "raze_music.h" #include "raze_music.h"
class MoviePlayer
{
public:
virtual void Start() {}
virtual bool Frame(uint64_t clock) = 0;
virtual void Stop() {}
virtual ~MoviePlayer() = default;
};
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// //
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
class DAnmPlayer : public DScreenJob class AnmPlayer : public MoviePlayer
{ {
// This doesn't need its own class type // This doesn't need its own class type
anim_t anim; anim_t anim;
@ -74,7 +83,7 @@ class DAnmPlayer : public DScreenJob
public: public:
bool isvalid() { return numframes > 0; } bool isvalid() { return numframes > 0; }
DAnmPlayer(FileReader& fr, const AnimSound* ans, const int *frameticks, bool nosoundcutoff) AnmPlayer(FileReader& fr, const AnimSound* ans, const int *frameticks, bool nosoundcutoff)
: animSnd(ans), frameTicks(frameticks), nostopsound(nosoundcutoff) : animSnd(ans), frameTicks(frameticks), nostopsound(nosoundcutoff)
{ {
buffer = fr.ReadPadded(1); buffer = fr.ReadPadded(1);
@ -94,7 +103,7 @@ public:
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override bool Frame(uint64_t clock) override
{ {
int currentclock = int(clock * 120 / 1'000'000'000); int currentclock = int(clock * 120 / 1'000'000'000);
@ -102,8 +111,7 @@ public:
{ {
twod->ClearScreen(); twod->ClearScreen();
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Masked, false, TAG_DONE); DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Masked, false, TAG_DONE);
if (skiprequest && !nostopsound) soundEngine->StopAllChannels(); return true;
return skiprequest? -1 : 1;
} }
animtex.SetFrame(ANIM_GetPalette(&anim), ANIM_DrawFrame(&anim, curframe)); animtex.SetFrame(ANIM_GetPalette(&anim), ANIM_DrawFrame(&anim, curframe));
@ -132,13 +140,18 @@ public:
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI, sound, 1.f, ATTN_NONE); soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI, sound, 1.f, ATTN_NONE);
} }
} }
if (!skiprequest && !nostopsound && curframe == numframes && soundEngine->GetSoundPlayingInfo(SOURCE_None, nullptr, -1)) return 1; if (!nostopsound && curframe == numframes && soundEngine->GetSoundPlayingInfo(SOURCE_None, nullptr, -1)) return true;
curframe++; curframe++;
if (skiprequest && !nostopsound) soundEngine->StopAllChannels(); return curframe < numframes;
return skiprequest ? -1 : curframe < numframes? 1 : 0;
} }
void OnDestroy() override void Stop() override
{
if (!nostopsound) soundEngine->StopAllChannels();
}
~AnmPlayer()
{ {
buffer.Reset(); buffer.Reset();
animtex.Clean(); animtex.Clean();
@ -151,7 +164,7 @@ public:
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
class DMvePlayer : public DScreenJob class MvePlayer : public MoviePlayer
{ {
InterplayDecoder decoder; InterplayDecoder decoder;
bool failed = false; bool failed = false;
@ -159,7 +172,7 @@ class DMvePlayer : public DScreenJob
public: public:
bool isvalid() { return !failed; } bool isvalid() { return !failed; }
DMvePlayer(FileReader& fr) : decoder(SoundEnabled()) MvePlayer(FileReader& fr) : decoder(SoundEnabled())
{ {
failed = !decoder.Open(fr); failed = !decoder.Open(fr);
} }
@ -170,17 +183,16 @@ public:
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override bool Frame(uint64_t clock) override
{ {
if (failed) return -1; if (failed) return false;
bool playon = decoder.RunFrame(clock); bool playon = decoder.RunFrame(clock);
twod->ClearScreen(); twod->ClearScreen();
DrawTexture(twod, decoder.animTex().GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE); DrawTexture(twod, decoder.animTex().GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
return playon;
return skiprequest ? -1 : playon ? 1 : 0;
} }
void OnDestroy() override ~MvePlayer()
{ {
decoder.Close(); decoder.Close();
} }
@ -192,7 +204,7 @@ public:
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
class DVpxPlayer : public DScreenJob class VpxPlayer : public MoviePlayer
{ {
bool failed = false; bool failed = false;
FileReader fr; FileReader fr;
@ -222,7 +234,7 @@ public:
public: public:
bool isvalid() { return !failed; } bool isvalid() { return !failed; }
DVpxPlayer(FileReader& fr_, const AnimSound* animSnd_, int origframedelay) VpxPlayer(FileReader& fr_, const AnimSound* animSnd_, int origframedelay, FString& error)
{ {
fr = std::move(fr_); fr = std::move(fr_);
animSnd = animSnd_; animSnd = animSnd_;
@ -230,7 +242,7 @@ public:
if (!ReadIVFHeader(origframedelay)) if (!ReadIVFHeader(origframedelay))
{ {
// We should never get here, because any file failing this has been eliminated before this constructor got called. // We should never get here, because any file failing this has been eliminated before this constructor got called.
Printf(PRINT_BOLD, "Failed reading IVF header\n"); error.Format("Failed reading IVF header\n");
failed = true; failed = true;
} }
@ -241,7 +253,7 @@ public:
vpx_codec_dec_cfg_t cfg = { 1, width, height }; vpx_codec_dec_cfg_t cfg = { 1, width, height };
if (vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0)) if (vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0))
{ {
Printf(PRINT_BOLD, "Error initializing VPX codec.\n"); error.Format("Error initializing VPX codec.\n");
failed = true; failed = true;
} }
} }
@ -387,16 +399,23 @@ public:
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override void Start() override
{ {
if (clock == 0) if (soundtrack > 0)
{ {
if (soundtrack > 0) Mus_Play(nullptr, fileSystem.GetFileFullName(soundtrack, false), false);
{
Mus_Play(nullptr, fileSystem.GetFileFullName(soundtrack, false), false);
}
animtex.SetSize(AnimTexture::YUV, width, height);
} }
animtex.SetSize(AnimTexture::YUV, width, height);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool Frame(uint64_t clock) override
{
bool stop = false; bool stop = false;
if (clock > nextframetime) if (clock > nextframetime)
{ {
@ -432,14 +451,18 @@ public:
} }
} }
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit, TAG_DONE); DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit, TAG_DONE);
if (stop || skiprequest) Mus_Stop(); return !stop;
if (stop) return 0;
return skiprequest ? -1 : 1;
} }
void OnDestroy() override void Stop()
{
Mus_Stop();
}
~VpxPlayer()
{ {
vpx_codec_destroy(&codec); vpx_codec_destroy(&codec);
animtex.Clean();
} }
}; };
@ -460,7 +483,7 @@ struct AudioData
int nRead; int nRead;
}; };
class DSmkPlayer : public DScreenJob class SmkPlayer : public MoviePlayer
{ {
SmackerHandle hSMK{}; SmackerHandle hSMK{};
int numAudioTracks; int numAudioTracks;
@ -484,7 +507,7 @@ public:
static bool StreamCallbackFunc(SoundStream* stream, void* buff, int len, void* userdata) static bool StreamCallbackFunc(SoundStream* stream, void* buff, int len, void* userdata)
{ {
DSmkPlayer* pId = (DSmkPlayer*)userdata; SmkPlayer* pId = (SmkPlayer*)userdata;
memcpy(buff, &pId->adata.samples[pId->adata.nRead], len); memcpy(buff, &pId->adata.samples[pId->adata.nRead], len);
pId->adata.nRead += len / 2; pId->adata.nRead += len / 2;
if (pId->adata.nRead >= countof(pId->adata.samples)) pId->adata.nRead = 0; if (pId->adata.nRead >= countof(pId->adata.samples)) pId->adata.nRead = 0;
@ -511,7 +534,7 @@ public:
} }
DSmkPlayer(const char *fn, const AnimSound* ans, bool fixedviewport) SmkPlayer(const char *fn, const AnimSound* ans, bool fixedviewport)
{ {
hSMK = Smacker_Open(fn); hSMK = Smacker_Open(fn);
if (!hSMK.isValid) if (!hSMK.isValid)
@ -551,23 +574,32 @@ public:
} }
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void Start() override
{
animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight);
}
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// //
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override bool Frame(uint64_t clock) override
{ {
int frame = clock / nFrameNs; int frame = clock / nFrameNs;
if (clock == 0)
{
animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight);
}
twod->ClearScreen(); twod->ClearScreen();
Printf("clock = %llu, frame = %d\n", clock, frame);
if (frame > nFrame) if (frame > nFrame)
{ {
Printf("Updating\n");
Smacker_GetPalette(hSMK, palette); Smacker_GetPalette(hSMK, palette);
Smacker_GetFrame(hSMK, pFrame.Data()); Smacker_GetFrame(hSMK, pFrame.Data());
animtex.SetFrame(palette, pFrame.Data()); animtex.SetFrame(palette, pFrame.Data());
@ -607,10 +639,10 @@ public:
} }
} }
return skiprequest ? -1 : nFrame < nFrames ? 1 : 0; return nFrame < nFrames;
} }
void OnDestroy() override ~SmkPlayer()
{ {
Smacker_Close(hSMK); Smacker_Close(hSMK);
if (stream) S_StopCustomStream(stream); if (stream) S_StopCustomStream(stream);
@ -625,13 +657,56 @@ public:
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* frameticks, bool nosoundcutoff) class DMoviePlayer : public DSkippableScreenJob
{ {
auto nothing = []()->DScreenJob* { return Create<DScreenJob>(); }; MoviePlayer* player;
if (!filename) bool started = false;
public:
DMoviePlayer(MoviePlayer* mp)
{ {
return nothing(); player = mp;
} }
void Draw(double smoothratio) override
{
if (!player)
{
state = stopped;
return;
}
if (!started)
{
started = true;
player->Start();
}
uint64_t clock = (ticks + smoothratio) * 1'000'000'000. / GameTicRate;
if (state == running && !player->Frame(clock))
{
state = finished;
}
}
void OnDestroy() override
{
if (player)
{
player->Stop();
delete player;
}
player = nullptr;
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
MoviePlayer* OpenMovie(const char* filename, const AnimSound* ans, const int* frameticks, bool nosoundcutoff, FString& error)
{
FileReader fr; FileReader fr;
// first try as .ivf - but only if sounds are provided - the decoder is video only. // first try as .ivf - but only if sounds are provided - the decoder is video only.
if (ans) if (ans)
@ -653,8 +728,8 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra
} }
if (!fr.isOpen()) if (!fr.isOpen())
{ {
Printf(PRINT_BOLD, "%s: Unable to open video\n", filename); error.Format("%s: Unable to open video\n", filename);
return nothing(); return nullptr;
} }
} }
char id[20] = {}; char id[20] = {};
@ -664,44 +739,44 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra
if (!memcmp(id, "LPF ", 4)) if (!memcmp(id, "LPF ", 4))
{ {
auto anm = Create<DAnmPlayer>(fr, ans, frameticks, nosoundcutoff); auto anm = new AnmPlayer(fr, ans, frameticks, nosoundcutoff);
if (!anm->isvalid()) if (!anm->isvalid())
{ {
Printf(PRINT_BOLD, "%s: invalid ANM file.\n", filename); error.Format("%s: invalid ANM file.\n", filename);
anm->Destroy(); delete anm;
return nothing(); return nullptr;
} }
return anm; return anm;
} }
else if (!memcmp(id, "SMK2", 4)) else if (!memcmp(id, "SMK2", 4))
{ {
fr.Close(); fr.Close();
auto anm = Create<DSmkPlayer>(filename, ans, true); // Fixme: Handle Blood's video scaling behavior more intelligently. auto anm = new SmkPlayer(filename, ans, true); // Fixme: Handle Blood's video scaling behavior more intelligently.
if (!anm->isvalid()) if (!anm->isvalid())
{ {
Printf(PRINT_BOLD, "%s: invalid SMK file.\n", filename); error.Format("%s: invalid SMK file.\n", filename);
anm->Destroy(); delete anm;
return nothing(); return nullptr;
} }
return anm; return anm;
} }
else if (!memcmp(id, "Interplay MVE File", 18)) else if (!memcmp(id, "Interplay MVE File", 18))
{ {
auto anm = Create<DMvePlayer>(fr); auto anm = new MvePlayer(fr);
if (!anm->isvalid()) if (!anm->isvalid())
{ {
anm->Destroy(); delete anm;
return nothing(); return nullptr;
} }
return anm; return anm;
} }
else if (!memcmp(id, "DKIF\0\0 \0VP80", 12)) else if (!memcmp(id, "DKIF\0\0 \0VP80", 12))
{ {
auto anm = Create<DVpxPlayer>(fr, ans, frameticks? frameticks[1] : 0 ); auto anm = new VpxPlayer(fr, ans, frameticks ? frameticks[1] : 0, error);
if (!anm->isvalid()) if (!anm->isvalid())
{ {
anm->Destroy(); delete anm;
return nothing(); return nullptr;
} }
anm->soundtrack = LookupMusic(filename, true); anm->soundtrack = LookupMusic(filename, true);
return anm; return anm;
@ -709,9 +784,31 @@ DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* fra
// add more formats here. // add more formats here.
else else
{ {
Printf(PRINT_BOLD, "%s: Unknown video format\n", filename); error.Format("%s: Unknown video format\n", filename);
return nullptr;
} }
return nothing(); }
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* frameticks, bool nosoundcutoff)
{
if (!filename)
{
return Create<DScreenJob>();
}
FString error;
auto movie = OpenMovie(filename, ans, frameticks, nosoundcutoff, error);
if (!movie)
{
Printf(TEXTCOLOR_YELLOW, "%s", error.GetChars());
return Create<DScreenJob>();
}
return Create<DMoviePlayer>(movie);
} }

View file

@ -57,6 +57,13 @@ IMPLEMENT_CLASS(DScreenJob, true, false)
IMPLEMENT_CLASS(DImageScreen, true, false) IMPLEMENT_CLASS(DImageScreen, true, false)
bool DSkippableScreenJob::OnEvent(event_t* evt)
{
if (evt->type == EV_GUI_KeyDown) state = skipped;
return true;
}
int DBlackScreen::Frame(uint64_t clock, bool skiprequest) int DBlackScreen::Frame(uint64_t clock, bool skiprequest)
{ {
int span = int(clock / 1'000'000); int span = int(clock / 1'000'000);
@ -193,7 +200,7 @@ public:
screenfade = 1.f; screenfade = 1.f;
} }
job.job->SetClock(clock); job.job->SetClock(clock);
int state = job.job->Frame(clock, skiprequest, 0); int state = job.job->Frame(clock, skiprequest, I_GetTimeFrac());
clock = job.job->GetClock(); clock = job.job->GetClock();
if (clock == 0) clock = 1; if (clock == 0) clock = 1;
return state; return state;
@ -227,7 +234,8 @@ public:
bool OnEvent(event_t* ev) bool OnEvent(event_t* ev)
{ {
if (jobs[index].job->state == DScreenJob::stopping) return false; if (index >= jobs.Size()) return false;
if (jobs[index].job->state != DScreenJob::running) return false;
return jobs[index].job->OnEvent(ev); return jobs[index].job->OnEvent(ev);
} }
@ -249,7 +257,7 @@ public:
} }
else else
{ {
if (jobs[index].job->state == DScreenJob::stopping) return; if (jobs[index].job->state != DScreenJob::running) return;
jobs[index].job->ticks++; jobs[index].job->ticks++;
jobs[index].job->OnTick(); jobs[index].job->OnTick();
} }
@ -282,6 +290,7 @@ public:
startTime = -1; startTime = -1;
clock = 0; clock = 0;
jobs[index].job->fadestate = DScreenJob::fadeout; jobs[index].job->fadestate = DScreenJob::fadeout;
jobs[index].job->state = DScreenJob::stopping;
gamestate = GS_INTRO; // block menu and console during fadeout - this can cause timing problems. gamestate = GS_INTRO; // block menu and console during fadeout - this can cause timing problems.
actionState = State_Fadeout; actionState = State_Fadeout;
} }
@ -296,6 +305,7 @@ public:
int ended = FadeoutFrame(); int ended = FadeoutFrame();
if (ended < 1) if (ended < 1)
{ {
jobs[index].job->state = DScreenJob::stopped;
AdvanceJob(terminateState < 0); AdvanceJob(terminateState < 0);
} }
} }

View file

@ -77,6 +77,21 @@ public:
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
class DSkippableScreenJob : public DScreenJob
{
protected:
DSkippableScreenJob(int fade = 0, float fadet = 250.f) : DScreenJob(fade, fadet)
{}
bool OnEvent(event_t* evt) override;
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DBlackScreen : public DScreenJob class DBlackScreen : public DScreenJob
{ {
int wait; int wait;