Add initial video playback support for Ogg Theora videos (resolves issue #66.)

The basic APIs are:

video = love.graphics.newVideo("myvideo.ogv")

love.graphics.draw(video, ...) -- Video objects are Drawables.

video:play(), video:pause()

video:getDuration(), video:tell(), video:rewind(), video:seek(seconds)

video:getSource()

video:getWidth(), video:getHeight(), video:setFilter(min, mag)

More advanced APIs include video:setSource(source), video:getStream(), and videostream:setSync.

To use a custom pixel shader when drawing a Video, call the new TexelVideo(texcoords) function instead of Texel(texture, texcoords) in order to get the pixel colors of a video frame.
This commit is contained in:
Bart van Strien
2015-12-08 22:41:19 -04:00
parent a1dab12117
commit 22f2175bec
41 changed files with 2241 additions and 20 deletions
+53
View File
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_VIDEO_VIDEO_H
#define LOVE_VIDEO_VIDEO_H
// LOVE
#include "common/Module.h"
#include "common/Stream.h"
#include "filesystem/File.h"
#include "VideoStream.h"
namespace love
{
namespace video
{
class Video : public Module
{
public:
virtual ~Video() {}
// Implements Module
virtual ModuleType getModuleType() const { return M_VIDEO; }
/**
* Create a VideoStream representing video frames
**/
virtual VideoStream *newVideoStream(love::filesystem::File *file) = 0;
}; // Video
} // video
} // love
#endif // LOVE_VIDEO_VIDEO_H
+168
View File
@@ -0,0 +1,168 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "VideoStream.h"
using love::thread::Lock;
namespace love
{
namespace video
{
void VideoStream::setSync(VideoStream::FrameSync *frameSync)
{
this->frameSync = frameSync;
}
VideoStream::FrameSync *VideoStream::getSync() const
{
return frameSync;
}
void VideoStream::play()
{
frameSync->play();
}
void VideoStream::pause()
{
frameSync->pause();
}
void VideoStream::seek(double offset)
{
frameSync->seek(offset);
}
double VideoStream::tell()
{
return frameSync->tell();
}
bool VideoStream::isPlaying()
{
return frameSync->isPlaying();
}
VideoStream::Frame::Frame()
: yplane(nullptr)
, cbplane(nullptr)
, crplane(nullptr)
{
}
VideoStream::Frame::~Frame()
{
delete[] yplane;
delete[] cbplane;
delete[] crplane;
}
void VideoStream::FrameSync::copyState(const VideoStream::FrameSync *other)
{
seek(other->tell());
if (other->isPlaying())
play();
else
pause();
}
double VideoStream::FrameSync::tell() const
{
return getPosition();
}
VideoStream::DeltaSync::DeltaSync()
: playing(false)
, position(0)
, speed(1)
{
}
VideoStream::DeltaSync::~DeltaSync()
{
}
double VideoStream::DeltaSync::getPosition() const
{
return position;
}
void VideoStream::DeltaSync::update(double dt)
{
Lock l(mutex);
if (playing)
position += dt*speed;
}
void VideoStream::DeltaSync::play()
{
playing = true;
}
void VideoStream::DeltaSync::pause()
{
playing = false;
}
void VideoStream::DeltaSync::seek(double time)
{
Lock l(mutex);
position = time;
}
bool VideoStream::DeltaSync::isPlaying() const
{
return playing;
}
VideoStream::SourceSync::SourceSync(love::audio::Source *source)
: source(source)
{
}
double VideoStream::SourceSync::getPosition() const
{
return source->tell(love::audio::Source::UNIT_SECONDS);
}
void VideoStream::SourceSync::play()
{
source->play();
}
void VideoStream::SourceSync::pause()
{
source->pause();
}
void VideoStream::SourceSync::seek(double time)
{
source->seek(time, love::audio::Source::UNIT_SECONDS);
}
bool VideoStream::SourceSync::isPlaying() const
{
return !source->isStopped() && !source->isPaused();
}
} // video
} // love
+131
View File
@@ -0,0 +1,131 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_VIDEO_VIDEOSTREAM_H
#define LOVE_VIDEO_VIDEOSTREAM_H
// LOVE
#include "common/Stream.h"
#include "audio/Source.h"
#include "thread/threads.h"
namespace love
{
namespace video
{
class VideoStream : public Stream
{
public:
virtual ~VideoStream() {}
virtual int getWidth() const = 0;
virtual int getHeight() const = 0;
virtual const std::string &getFilename() const = 0;
// Playback api
virtual void play();
virtual void pause();
virtual void seek(double offset);
virtual double tell();
virtual bool isPlaying();
class FrameSync;
class DeltaSync;
// The stream now owns the sync, do not reuse or free
virtual void setSync(FrameSync *frameSync);
virtual FrameSync *getSync() const;
// Data structures
struct Frame
{
Frame();
~Frame();
int yw, yh;
unsigned char *yplane;
int cw, ch;
unsigned char *cbplane;
unsigned char *crplane;
};
class FrameSync : public Object
{
public:
virtual double getPosition() const = 0;
virtual void update(double /*dt*/) {}
virtual ~FrameSync() {}
void copyState(const FrameSync *other);
// Playback api
virtual void play() = 0;
virtual void pause() = 0;
virtual void seek(double offset) = 0;
virtual double tell() const;
virtual bool isPlaying() const = 0;
};
class DeltaSync : public FrameSync
{
public:
DeltaSync();
~DeltaSync();
virtual double getPosition() const override;
virtual void update(double dt) override;
virtual void play() override;
virtual void pause() override;
virtual void seek(double time) override;
virtual bool isPlaying() const override;
private:
bool playing;
double position;
double speed;
love::thread::MutexRef mutex;
};
class SourceSync : public FrameSync
{
public:
SourceSync(love::audio::Source *source);
virtual double getPosition() const override;
virtual void play() override;
virtual void pause() override;
virtual void seek(double time) override;
virtual bool isPlaying() const override;
private:
StrongRef<love::audio::Source> source;
};
protected:
StrongRef<FrameSync> frameSync;
};
} // video
} // love
#endif // LOVE_VIDEO_VIDEOSTREAM_H
+122
View File
@@ -0,0 +1,122 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// STL
#include <vector>
// LOVE
#include "Video.h"
#include "common/delay.h"
#include "timer/Timer.h"
namespace love
{
namespace video
{
namespace theora
{
Video::Video()
{
workerThread = new Worker();
workerThread->start();
}
Video::~Video()
{
delete workerThread;
}
VideoStream *Video::newVideoStream(love::filesystem::File *file)
{
VideoStream *stream = new VideoStream(file);
workerThread->addStream(stream);
return stream;
}
const char *Video::getName() const
{
return "love.video.theora";
}
Worker::Worker()
: stopping(false)
{
threadName = "VideoWorker";
}
Worker::~Worker()
{
stop();
}
void Worker::addStream(VideoStream *stream)
{
love::thread::Lock l(mutex);
streams.push_back(stream);
}
void Worker::stop()
{
{
love::thread::Lock l(mutex);
stopping = true;
}
owner->wait();
}
void Worker::threadFunction()
{
double lastFrame = love::timer::Timer::getTimeSinceEpoch();
while (true)
{
double curFrame = love::timer::Timer::getTimeSinceEpoch();
double dt = curFrame-lastFrame;
lastFrame = curFrame;
{
love::thread::Lock l(mutex);
if (stopping)
return;
for (auto it = streams.begin(); it != streams.end(); ++it)
{
VideoStream *stream = *it;
if (stream->getReferenceCount() == 1)
{
// We're the only ones left
streams.erase(it);
break;
}
stream->threadedFillBackBuffer(dt);
}
}
// sleep
love::delay(2);
}
}
} // theora
} // video
} // love
+81
View File
@@ -0,0 +1,81 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_VIDEO_THEORA_VIDEO_H
#define LOVE_VIDEO_THEORA_VIDEO_H
// STL
#include <vector>
// LOVE
#include "filesystem/File.h"
#include "video/Video.h"
#include "thread/threads.h"
#include "VideoStream.h"
namespace love
{
namespace video
{
namespace theora
{
class Worker;
class Video : public love::video::Video
{
public:
Video();
~Video();
// Implements Module
virtual const char *getName() const;
VideoStream *newVideoStream(love::filesystem::File* file);
private:
Worker *workerThread;
}; // Video
class Worker : public love::thread::Threadable
{
public:
Worker();
~Worker();
// Implements Threadable
void threadFunction();
void addStream(VideoStream *stream);
// Frees itself!
void stop();
private:
std::vector<StrongRef<VideoStream>> streams;
love::thread::MutexRef mutex;
volatile bool stopping;
}; // Worker
} // theora
} // video
} // love
#endif // LOVE_VIDEO_THEORA_VIDEO_H
+402
View File
@@ -0,0 +1,402 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// STL
#include <iostream>
// LOVE
#include "VideoStream.h"
using love::filesystem::File;
namespace love
{
namespace video
{
namespace theora
{
VideoStream::VideoStream(love::filesystem::File *file)
: file(file)
, headerParsed(false)
, streamInited(false)
, videoSerial(0)
, decoder(nullptr)
, frameReady(false)
, lastFrame(0)
, nextFrame(0)
, eos(false)
, lagCounter(0)
{
ogg_sync_init(&sync);
th_info_init(&videoInfo);
frontBuffer = new Frame();
backBuffer = new Frame();
try
{
parseHeader();
}
catch (love::Exception &ex)
{
delete backBuffer;
delete frontBuffer;
th_info_clear(&videoInfo);
ogg_sync_clear(&sync);
throw ex;
}
frameSync = new DeltaSync();
frameSync->release();
}
VideoStream::~VideoStream()
{
if (decoder)
th_decode_free(decoder);
th_info_clear(&videoInfo);
if (headerParsed)
ogg_stream_clear(&stream);
ogg_sync_clear(&sync);
delete frontBuffer;
delete backBuffer;
}
int VideoStream::getWidth() const
{
if (headerParsed)
return videoInfo.pic_width;
else
return 0;
}
int VideoStream::getHeight() const
{
if (headerParsed)
return videoInfo.pic_height;
else
return 0;
}
const std::string &VideoStream::getFilename() const
{
return file->getFilename();
}
void VideoStream::setSync(FrameSync *frameSync)
{
love::thread::Lock l(bufferMutex);
this->frameSync = frameSync;
}
const void *VideoStream::getFrontBuffer() const
{
return frontBuffer;
}
size_t VideoStream::getSize() const
{
return sizeof(Frame);
}
void VideoStream::readPage()
{
char *syncBuffer = nullptr;
while (ogg_sync_pageout(&sync, &page) != 1)
{
if (syncBuffer && !headerParsed && ogg_stream_check(&stream))
throw love::Exception("Invalid stream");
syncBuffer = ogg_sync_buffer(&sync, 8192);
size_t read = file->read(syncBuffer, 8192);
ogg_sync_wrote(&sync, read);
}
}
bool VideoStream::readPacket(bool mustSucceed)
{
if (!streamInited)
{
readPage();
videoSerial = ogg_page_serialno(&page);
ogg_stream_init(&stream, videoSerial);
streamInited = true;
ogg_stream_pagein(&stream, &page);
}
while (ogg_stream_packetout(&stream, &packet) != 1)
{
// We need to read another page, but there is none, we're at the end
if (ogg_page_eos(&page) && !mustSucceed)
return eos = true;
do
{
readPage();
} while (ogg_page_serialno(&page) != videoSerial);
ogg_stream_pagein(&stream, &page);
}
return false;
}
template<typename T>
inline void scaleFormat(th_pixel_fmt fmt, T &x, T &y)
{
switch(fmt)
{
case TH_PF_420:
y /= 2;
case TH_PF_422:
x /= 2;
break;
default:
break;
}
}
void VideoStream::parseHeader()
{
if (headerParsed)
return;
th_comment comment;
th_setup_info *setupInfo = nullptr;
th_comment_init(&comment);
int ret;
do
{
readPacket();
ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
if (ret == TH_ENOTFORMAT)
{
ogg_stream_clear(&stream);
streamInited = false;
}
} while(ret < 0 && !ogg_page_eos(&page));
if (ret < 0)
{
th_comment_clear(&comment);
throw love::Exception("Could not find header");
}
while (ret > 0)
{
readPacket();
ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
}
th_comment_clear(&comment);
decoder = th_decode_alloc(&videoInfo, setupInfo);
th_setup_free(setupInfo);
Frame *buffers[2] = {backBuffer, frontBuffer};
yPlaneXOffset = cPlaneXOffset = videoInfo.pic_x;
yPlaneYOffset = cPlaneYOffset = videoInfo.pic_y;
scaleFormat(videoInfo.pixel_fmt, cPlaneXOffset, cPlaneYOffset);
for (int i = 0; i < 2; i++)
{
buffers[i]->cw = buffers[i]->yw = videoInfo.pic_width;
buffers[i]->ch = buffers[i]->yh = videoInfo.pic_height;
scaleFormat(videoInfo.pixel_fmt, buffers[i]->cw, buffers[i]->ch);
buffers[i]->yplane = new unsigned char[buffers[i]->yw * buffers[i]->yh];
buffers[i]->cbplane = new unsigned char[buffers[i]->cw * buffers[i]->ch];
buffers[i]->crplane = new unsigned char[buffers[i]->cw * buffers[i]->ch];
memset(buffers[i]->yplane, 16, buffers[i]->yw * buffers[i]->yh);
memset(buffers[i]->cbplane, 128, buffers[i]->cw * buffers[i]->ch);
memset(buffers[i]->crplane, 128, buffers[i]->cw * buffers[i]->ch);
}
headerParsed = true;
th_decode_packetin(decoder, &packet, nullptr);
}
// Arbitrary seeking isn't supported yet, but rewinding is
void VideoStream::rewind()
{
// Seek our data stream back to the start
file->seek(0);
// Break our sync, and discard the rest of the page
ogg_sync_reset(&sync);
ogg_sync_pageseek(&sync, &page);
// Read our first page/packet from the stream again
readPacket(true);
// Now tell theora we're at frame 1 (not 0!)
int64 granPos = 1;
th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &granPos, sizeof(granPos));
// Force a redraw, since this will always be less than the sync's position
lastFrame = nextFrame = -1;
eos = false;
}
void VideoStream::seekDecoder(double target)
{
double low = 0;
double high = file->getSize();
while (high-low > 0.0001)
{
// Determine our next binary search position
double pos = (high-low)/2+low;
file->seek(pos);
// Break sync
ogg_sync_reset(&sync);
ogg_sync_pageseek(&sync, &page);
// Read a packet
readPacket(true);
// Determine if this is the right place
double curTime = th_granule_time(decoder, packet.granulepos);
if (curTime > target && th_granule_time(decoder, packet.granulepos-1) < target)
break;
else if (curTime > target)
high = pos;
else
low = pos;
}
// Now update theora and our decoder on this new position of ours
lastFrame = nextFrame = -1;
eos = false;
th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &packet.granulepos, sizeof(packet.granulepos));
}
void VideoStream::threadedFillBackBuffer(double dt)
{
// Synchronize
frameSync->update(dt);
double position = frameSync->getPosition();
// Seeking backwards
if (position < lastFrame)
{
if (position < 0.01)
rewind();
else
seekDecoder(position);
}
// If we're at the end of the stream, or if we're displaying the right frame
// stop here
if (eos || position < nextFrame)
return;
th_ycbcr_buffer bufferinfo;
th_decode_ycbcr_out(decoder, bufferinfo);
ogg_int64_t granulePosition;
do
{
if (readPacket())
return;
} while (th_decode_packetin(decoder, &packet, &granulePosition) != 0);
lastFrame = nextFrame;
nextFrame = th_granule_time(decoder, granulePosition);
{
// Don't swap whilst we're writing to the backbuffer
love::thread::Lock l(bufferMutex);
frameReady = false;
}
for (int y = 0; y < backBuffer->yh; ++y)
{
memcpy(backBuffer->yplane+backBuffer->yw*y,
bufferinfo[0].data+
bufferinfo[0].stride*(y+yPlaneYOffset)+yPlaneXOffset,
backBuffer->yw);
}
for (int y = 0; y < backBuffer->ch; ++y)
{
memcpy(backBuffer->cbplane+backBuffer->cw*y,
bufferinfo[1].data+
bufferinfo[1].stride*(y+cPlaneYOffset)+cPlaneXOffset,
backBuffer->cw);
}
for (int y = 0; y < backBuffer->ch; ++y)
{
memcpy(backBuffer->crplane+backBuffer->cw*y,
bufferinfo[2].data+
bufferinfo[2].stride*(y+cPlaneYOffset)+cPlaneXOffset,
backBuffer->cw);
}
// Seeking forwards:
// If we're still not on the right frame, either we're lagging or we're seeking
// After 5 frames, go for a seek. This is not ideal.. but what is
if (position > nextFrame)
{
if (++lagCounter > 5)
seek(position);
}
else
lagCounter = 0;
love::thread::Lock l(bufferMutex);
frameReady = true;
}
void VideoStream::fillBackBuffer()
{
// Done in worker thread
}
bool VideoStream::swapBuffers()
{
if (eos)
return false;
love::thread::Lock l(bufferMutex);
if (!frameReady)
return false;
frameReady = false;
Frame *temp = frontBuffer;
frontBuffer = backBuffer;
backBuffer = temp;
return true;
}
} // theora
} // video
} // love
+101
View File
@@ -0,0 +1,101 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_VIDEO_THEORA_VIDEOSTREAM_H
#define LOVE_VIDEO_THEORA_VIDEOSTREAM_H
#include "video/VideoStream.h"
// LOVE
#include "common/int.h"
#include "filesystem/File.h"
#include "thread/threads.h"
// OGG/Theora
#include <ogg/ogg.h>
#include <theora/codec.h>
#include <theora/theoradec.h>
namespace love
{
namespace video
{
namespace theora
{
class VideoStream : public love::video::VideoStream
{
public:
VideoStream(love::filesystem::File *file);
~VideoStream();
const void *getFrontBuffer() const;
size_t getSize() const;
void fillBackBuffer();
bool swapBuffers();
int getWidth() const;
int getHeight() const;
const std::string &getFilename() const;
void setSync(FrameSync *frameSync);
void threadedFillBackBuffer(double dt);
private:
StrongRef<love::filesystem::File> file;
bool headerParsed;
bool streamInited;
int videoSerial;
ogg_sync_state sync;
ogg_stream_state stream;
ogg_page page;
ogg_packet packet;
th_info videoInfo;
th_dec_ctx *decoder;
Frame *frontBuffer;
Frame *backBuffer;
unsigned int yPlaneXOffset;
unsigned int cPlaneXOffset;
unsigned int yPlaneYOffset;
unsigned int cPlaneYOffset;
love::thread::MutexRef bufferMutex;
bool frameReady;
double lastFrame;
double nextFrame;
bool eos;
unsigned int lagCounter;
void readPage();
bool readPacket(bool mustSucceed = false); // true if eos
void parseHeader();
void rewind();
void seekDecoder(double target);
}; // VideoStream
} // theora
} // video
} // love
#endif // LOVE_VIDEO_THEORA_VIDEOSTREAM_H
+86
View File
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "filesystem/wrap_Filesystem.h"
#include "theora/Video.h"
#include "wrap_Video.h"
#include "wrap_VideoStream.h"
namespace love
{
namespace video
{
#define instance() (Module::getInstance<Video>(Module::M_VIDEO))
int w_newVideoStream(lua_State *L)
{
love::filesystem::File *file = love::filesystem::luax_getfile(L, 1);
VideoStream *stream = nullptr;
luax_catchexcept(L, [&]() {
// Can't check if open for reading
if (!file->isOpen() && !file->open(love::filesystem::File::MODE_READ))
luaL_error(L, "File is not open and cannot be opened");
stream = instance()->newVideoStream(file);
});
luax_pushtype(L, VIDEO_VIDEO_STREAM_ID, stream);
stream->release();
return 1;
}
static const lua_CFunction types[] =
{
luaopen_videostream,
0
};
static const luaL_Reg functions[] =
{
{ "newVideoStream", w_newVideoStream },
{ 0, 0 }
};
extern "C" int luaopen_love_video(lua_State *L)
{
Video *instance = instance();
if (instance == nullptr)
{
luax_catchexcept(L, [&](){ instance = new love::video::theora::Video(); });
}
else
instance->retain();
WrappedModule w;
w.module = instance;
w.name = "video";
w.type = MODULE_ID;
w.functions = functions;
w.types = types;
return luax_register_module(L, w);
}
} // video
} // love
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_VIDEO_WRAP_VIDEO_H
#define LOVE_VIDEO_WRAP_VIDEO_H
// LOVE
#include "VideoStream.h"
#include "common/runtime.h"
namespace love
{
namespace video
{
extern "C" LOVE_EXPORT int luaopen_love_video(lua_State *L);
} // video
} // love
#endif // LOVE_VIDEO_WRAP_VIDEO_H
+131
View File
@@ -0,0 +1,131 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "wrap_VideoStream.h"
namespace love
{
namespace video
{
VideoStream *luax_checkvideostream(lua_State *L, int idx)
{
return luax_checktype<VideoStream>(L, idx, VIDEO_VIDEO_STREAM_ID);
}
int w_VideoStream_setSync(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
if (luax_istype(L, 2, AUDIO_SOURCE_ID))
{
auto src = luax_totype<love::audio::Source>(L, 2, AUDIO_SOURCE_ID);
auto sync = new VideoStream::SourceSync(src);
stream->setSync(sync);
sync->release();
}
else if (luax_istype(L, 2, VIDEO_VIDEO_STREAM_ID))
{
auto other = luax_totype<VideoStream>(L, 2, VIDEO_VIDEO_STREAM_ID);
stream->setSync(other->getSync());
}
else if (lua_isnoneornil(L, 2))
{
auto newSync = new VideoStream::DeltaSync();
newSync->copyState(stream->getSync());
stream->setSync(newSync);
newSync->release();
}
else
return luax_typerror(L, 2, "Source or VideoStream or nil");
return 0;
}
int w_VideoStream_getFilename(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
luax_pushstring(L, stream->getFilename());
return 1;
}
int w_VideoStream_play(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
stream->play();
return 0;
}
int w_VideoStream_pause(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
stream->pause();
return 0;
}
int w_VideoStream_seek(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
double offset = luaL_checknumber(L, 2);
stream->seek(offset);
return 0;
}
int w_VideoStream_rewind(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
stream->seek(0);
return 0;
}
int w_VideoStream_tell(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
lua_pushnumber(L, stream->tell());
return 1;
}
int w_VideoStream_isPlaying(lua_State *L)
{
auto stream = luax_checkvideostream(L, 1);
luax_pushboolean(L, stream->isPlaying());
return 1;
}
static const luaL_Reg videostream_functions[] =
{
{ "setSync", w_VideoStream_setSync },
{ "getFilename", w_VideoStream_getFilename },
{ "play", w_VideoStream_play },
{ "pause", w_VideoStream_pause },
{ "seek", w_VideoStream_seek },
{ "rewind", w_VideoStream_rewind },
{ "tell", w_VideoStream_tell },
{ "isPlaying", w_VideoStream_isPlaying },
{ 0, 0 }
};
int luaopen_videostream(lua_State *L)
{
return luax_register_type(L, VIDEO_VIDEO_STREAM_ID, "VideoStream", videostream_functions, nullptr);
}
} // video
} // love
+35
View File
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "common/runtime.h"
#include "VideoStream.h"
namespace love
{
namespace video
{
LOVE_EXPORT int luaopen_videostream(lua_State *L);
} // video
} // love