aboutsummaryrefslogtreecommitdiff
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/avi_decoder.cpp327
-rw-r--r--video/avi_decoder.h20
-rw-r--r--video/video_decoder.cpp45
-rw-r--r--video/video_decoder.h23
4 files changed, 353 insertions, 62 deletions
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp
index beb68fc969..aee2d88988 100644
--- a/video/avi_decoder.cpp
+++ b/video/avi_decoder.cpp
@@ -80,6 +80,13 @@ namespace Video {
#define ID_DUCK MKTAG('D','U','C','K')
#define ID_MPG2 MKTAG('m','p','g','2')
+// Stream Types
+enum {
+ kStreamTypePaletteChange = MKTAG16('p', 'c'),
+ kStreamTypeRawVideo = MKTAG16('d', 'b'),
+ kStreamTypeAudio = MKTAG16('w', 'b')
+};
+
AVIDecoder::AVIDecoder(Audio::Mixer::SoundType soundType) : _frameRateOverride(0), _soundType(soundType) {
initCommon();
@@ -106,6 +113,12 @@ void AVIDecoder::initCommon() {
memset(&_header, 0, sizeof(_header));
}
+bool AVIDecoder::isSeekable() const {
+ // Only videos with an index can seek
+ // Anyone else who wants to seek is crazy.
+ return isVideoLoaded() && !_indexEntries.empty();
+}
+
bool AVIDecoder::parseNextChunk() {
uint32 tag = _fileStream->readUint32BE();
uint32 size = _fileStream->readUint32LE();
@@ -150,10 +163,10 @@ bool AVIDecoder::parseNextChunk() {
OldIndex indexEntry;
indexEntry.id = _fileStream->readUint32BE();
indexEntry.flags = _fileStream->readUint32LE();
- indexEntry.offset = _fileStream->readUint32LE();
+ indexEntry.offset = _fileStream->readUint32LE() + _movieListStart - 4; // Adjust to absolute
indexEntry.size = _fileStream->readUint32LE();
_indexEntries.push_back(indexEntry);
- debug(0, "Index %d == Tag \'%s\', Offset = %d, Size = %d", i, tag2str(indexEntry.id), indexEntry.offset, indexEntry.size);
+ debug(0, "Index %d == Tag \'%s\', Offset = %d, Size = %d (Flags = %d)", i, tag2str(indexEntry.id), indexEntry.offset, indexEntry.size, indexEntry.flags);
}
break;
default:
@@ -187,7 +200,7 @@ void AVIDecoder::handleList(uint32 listSize) {
_decodedHeader = true;
break;
case ID_INFO: // Metadata
- case ID_PRMI: // Unknown (ZEngine)
+ case ID_PRMI: // Unknown metadata, should be safe to ignore
// Ignore metadata
_fileStream->skip(listSize);
return;
@@ -254,21 +267,22 @@ void AVIDecoder::handleStreamHeader(uint32 size) {
if (sHeader.streamHandler == 0)
sHeader.streamHandler = bmInfo.compression;
- AVIVideoTrack *track = new AVIVideoTrack(_header.totalFrames, sHeader, bmInfo);
+ byte *initialPalette = 0;
if (bmInfo.bitCount == 8) {
- byte *palette = const_cast<byte *>(track->getPalette());
+ initialPalette = new byte[256 * 3];
+ memset(initialPalette, 0, 256 * 3);
+
+ byte *palette = initialPalette;
for (uint32 i = 0; i < bmInfo.clrUsed; i++) {
palette[i * 3 + 2] = _fileStream->readByte();
palette[i * 3 + 1] = _fileStream->readByte();
palette[i * 3] = _fileStream->readByte();
_fileStream->readByte();
}
-
- track->markPaletteDirty();
}
- addTrack(track);
+ addTrack(new AVIVideoTrack(_header.totalFrames, sHeader, bmInfo, initialPalette));
} else if (sHeader.streamType == ID_AUDS) {
PCMWaveFormat wvInfo;
wvInfo.tag = _fileStream->readUint16LE();
@@ -383,7 +397,7 @@ void AVIDecoder::readNextPacket() {
}
if (track->getTrackType() == Track::kTrackTypeAudio) {
- if (getStreamType(nextTag) != MKTAG16('w', 'b'))
+ if (getStreamType(nextTag) != kStreamTypeAudio)
error("Invalid audio track tag '%s'", tag2str(nextTag));
assert(chunk);
@@ -391,29 +405,10 @@ void AVIDecoder::readNextPacket() {
} else {
AVIVideoTrack *videoTrack = (AVIVideoTrack *)track;
- if (getStreamType(nextTag) == MKTAG16('p', 'c')) {
+ if (getStreamType(nextTag) == kStreamTypePaletteChange) {
// Palette Change
- assert(chunk);
- byte firstEntry = chunk->readByte();
- uint16 numEntries = chunk->readByte();
- chunk->readUint16LE(); // Reserved
-
- // 0 entries means all colors are going to be changed
- if (numEntries == 0)
- numEntries = 256;
-
- byte *palette = const_cast<byte *>(videoTrack->getPalette());
-
- for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
- palette[i * 3] = chunk->readByte();
- palette[i * 3 + 1] = chunk->readByte();
- palette[i * 3 + 2] = chunk->readByte();
- chunk->readByte(); // Flags that don't serve us any purpose
- }
-
- delete chunk;
- videoTrack->markPaletteDirty();
- } else if (getStreamType(nextTag) == MKTAG16('d', 'b')) {
+ videoTrack->loadPaletteFromChunk(chunk);
+ } else if (getStreamType(nextTag) == kStreamTypeRawVideo) {
// TODO: Check if this really is uncompressed. Many videos
// falsely put compressed data in here.
error("Uncompressed AVI frame found");
@@ -424,6 +419,201 @@ void AVIDecoder::readNextPacket() {
}
}
+bool AVIDecoder::rewind() {
+ if (!VideoDecoder::rewind())
+ return false;
+
+ _fileStream->seek(_movieListStart);
+ return true;
+}
+
+bool AVIDecoder::seekIntern(const Audio::Timestamp &time) {
+ // Can't seek beyond the end
+ if (time > getDuration())
+ return false;
+
+ // Track down our video track (optionally audio too).
+ // We only support seeking with one track right now.
+ AVIVideoTrack *videoTrack = 0;
+ AVIAudioTrack *audioTrack = 0;
+ int videoIndex = -1;
+ int audioIndex = -1;
+ uint trackID = 0;
+
+ for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, trackID++) {
+ if ((*it)->getTrackType() == Track::kTrackTypeVideo) {
+ if (videoTrack) {
+ // Already have one
+ // -> Not supported
+ return false;
+ }
+
+ videoTrack = (AVIVideoTrack *)*it;
+ videoIndex = trackID;
+ } else if ((*it)->getTrackType() == Track::kTrackTypeAudio) {
+ if (audioTrack) {
+ // Already have one
+ // -> Not supported
+ return false;
+ }
+
+ audioTrack = (AVIAudioTrack *)*it;
+ audioIndex = trackID;
+ }
+ }
+
+ // Need a video track to go forwards
+ // If there isn't a video track, why would anyone be using AVI then?
+ if (!videoTrack)
+ return false;
+
+ // If we seek directly to the end, just mark the tracks as over
+ if (time == getDuration()) {
+ videoTrack->setCurFrame(videoTrack->getFrameCount() - 1);
+
+ if (audioTrack)
+ audioTrack->resetStream();
+
+ return true;
+ }
+
+ // Get the frame we should be on at this time
+ uint frame = videoTrack->getFrameAtTime(time);
+
+ // Reset any palette, if necessary
+ videoTrack->useInitialPalette();
+
+ int lastKeyFrame = -1;
+ int frameIndex = -1;
+ int lastRecord = -1;
+ uint curFrame = 0;
+
+ // Go through and figure out where we should be
+ // If there's a palette, we need to find the palette too
+ for (uint32 i = 0; i < _indexEntries.size(); i++) {
+ const OldIndex &index = _indexEntries[i];
+
+ if (index.id == ID_REC) {
+ // Keep track of any records we find
+ lastRecord = i;
+ } else {
+ if (getStreamIndex(index.id) != videoIndex)
+ continue;
+
+ uint16 streamType = getStreamType(index.id);
+
+ if (streamType == kStreamTypePaletteChange) {
+ // We need to handle any palette change we see since there's no
+ // flag to tell if this is a "key" palette.
+ // Decode the palette
+ _fileStream->seek(_indexEntries[i].offset + 8);
+ Common::SeekableReadStream *chunk = 0;
+
+ if (_indexEntries[i].size != 0)
+ chunk = _fileStream->readStream(_indexEntries[i].size);
+
+ videoTrack->loadPaletteFromChunk(chunk);
+ } else {
+ // Check to see if this is a keyframe
+ // The first frame has to be a keyframe
+ if ((_indexEntries[i].flags & AVIIF_INDEX) || curFrame == 0)
+ lastKeyFrame = i;
+
+ // Did we find the target frame?
+ if (frame == curFrame) {
+ frameIndex = i;
+ break;
+ }
+
+ curFrame++;
+ }
+ }
+ }
+
+ if (frameIndex < 0) // This shouldn't happen.
+ return false;
+
+ if (audioTrack) {
+ // We need to find where the start of audio should be.
+ // Which is exactly 'initialFrames' audio chunks back from where
+ // our found frame is.
+
+ // Recreate the audio stream
+ audioTrack->resetStream();
+
+ uint framesNeeded = _header.initialFrames;
+ uint startAudioChunk = 0;
+ int startAudioSearch = (lastRecord < 0) ? (frameIndex - 1) : (lastRecord - 1);
+
+ for (int i = startAudioSearch; i >= 0; i--) {
+ if (getStreamIndex(_indexEntries[i].id) != audioIndex)
+ continue;
+
+ assert(getStreamType(_indexEntries[i].id) == kStreamTypeAudio);
+
+ framesNeeded--;
+
+ if (framesNeeded == 0) {
+ startAudioChunk = i;
+ break;
+ }
+ }
+
+ // Now go forward and queue them all
+ for (int i = startAudioChunk; i <= startAudioSearch; i++) {
+ if (_indexEntries[i].id == ID_REC)
+ continue;
+
+ if (getStreamIndex(_indexEntries[i].id) != audioIndex)
+ continue;
+
+ assert(getStreamType(_indexEntries[i].id) == kStreamTypeAudio);
+
+ _fileStream->seek(_indexEntries[i].offset + 8);
+ Common::SeekableReadStream *chunk = _fileStream->readStream(_indexEntries[i].size);
+ audioTrack->queueSound(chunk);
+ }
+
+ // Skip any audio to bring us to the right time
+ audioTrack->skipAudio(time, videoTrack->getFrameTime(frame));
+ }
+
+ // Decode from keyFrame to curFrame - 1
+ for (int i = lastKeyFrame; i < frameIndex; i++) {
+ if (_indexEntries[i].id == ID_REC)
+ continue;
+
+ if (getStreamIndex(_indexEntries[i].id) != videoIndex)
+ continue;
+
+ uint16 streamType = getStreamType(_indexEntries[i].id);
+
+ // Ignore palettes, they were already handled
+ if (streamType == kStreamTypePaletteChange)
+ continue;
+
+ // Frame, hopefully
+ _fileStream->seek(_indexEntries[i].offset + 8);
+ Common::SeekableReadStream *chunk = 0;
+
+ if (_indexEntries[i].size != 0)
+ chunk = _fileStream->readStream(_indexEntries[i].size);
+
+ videoTrack->decodeFrame(chunk);
+ }
+
+ // Seek to the right spot
+ // To the beginning of the last record, or frame if that doesn't exist
+ if (lastRecord >= 0)
+ _fileStream->seek(_indexEntries[lastRecord].offset);
+ else
+ _fileStream->seek(_indexEntries[frameIndex].offset);
+
+ videoTrack->setCurFrame((int)frame - 1);
+
+ return true;
+}
+
byte AVIDecoder::getStreamIndex(uint32 tag) const {
char string[3];
WRITE_BE_UINT16(string, tag >> 16);
@@ -431,17 +621,18 @@ byte AVIDecoder::getStreamIndex(uint32 tag) const {
return strtol(string, 0, 16);
}
-AVIDecoder::AVIVideoTrack::AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader)
- : _frameCount(frameCount), _vidsHeader(streamHeader), _bmInfo(bitmapInfoHeader) {
- memset(_palette, 0, sizeof(_palette));
+AVIDecoder::AVIVideoTrack::AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader, byte *initialPalette)
+ : _frameCount(frameCount), _vidsHeader(streamHeader), _bmInfo(bitmapInfoHeader), _initialPalette(initialPalette) {
_videoCodec = createCodec();
- _dirtyPalette = false;
_lastFrame = 0;
_curFrame = -1;
+
+ useInitialPalette();
}
AVIDecoder::AVIVideoTrack::~AVIVideoTrack() {
delete _videoCodec;
+ delete[] _initialPalette;
}
void AVIDecoder::AVIVideoTrack::decodeFrame(Common::SeekableReadStream *stream) {
@@ -464,6 +655,47 @@ Graphics::PixelFormat AVIDecoder::AVIVideoTrack::getPixelFormat() const {
return Graphics::PixelFormat();
}
+void AVIDecoder::AVIVideoTrack::loadPaletteFromChunk(Common::SeekableReadStream *chunk) {
+ assert(chunk);
+ byte firstEntry = chunk->readByte();
+ uint16 numEntries = chunk->readByte();
+ chunk->readUint16LE(); // Reserved
+
+ // 0 entries means all colors are going to be changed
+ if (numEntries == 0)
+ numEntries = 256;
+
+ for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
+ _palette[i * 3] = chunk->readByte();
+ _palette[i * 3 + 1] = chunk->readByte();
+ _palette[i * 3 + 2] = chunk->readByte();
+ chunk->readByte(); // Flags that don't serve us any purpose
+ }
+
+ delete chunk;
+ _dirtyPalette = true;
+}
+
+void AVIDecoder::AVIVideoTrack::useInitialPalette() {
+ _dirtyPalette = false;
+
+ if (_initialPalette) {
+ memcpy(_palette, _initialPalette, sizeof(_palette));
+ _dirtyPalette = true;
+ }
+}
+
+bool AVIDecoder::AVIVideoTrack::rewind() {
+ _curFrame = -1;
+
+ useInitialPalette();
+
+ delete _videoCodec;
+ _videoCodec = createCodec();
+ _lastFrame = 0;
+ return true;
+}
+
Codec *AVIDecoder::AVIVideoTrack::createCodec() {
switch (_vidsHeader.streamHandler) {
case ID_CRAM:
@@ -525,6 +757,31 @@ void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) {
}
}
+void AVIDecoder::AVIAudioTrack::skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime) {
+ Audio::Timestamp timeDiff = time.convertToFramerate(_wvInfo.samplesPerSec) - frameTime.convertToFramerate(_wvInfo.samplesPerSec);
+ int skipFrames = timeDiff.totalNumberOfFrames();
+
+ if (skipFrames <= 0)
+ return;
+
+ if (_audStream->isStereo())
+ skipFrames *= 2;
+
+ int16 *tempBuffer = new int16[skipFrames];
+ _audStream->readBuffer(tempBuffer, skipFrames);
+ delete[] tempBuffer;
+}
+
+void AVIDecoder::AVIAudioTrack::resetStream() {
+ delete _audStream;
+ _audStream = createAudioStream();
+}
+
+bool AVIDecoder::AVIAudioTrack::rewind() {
+ resetStream();
+ return true;
+}
+
Audio::AudioStream *AVIDecoder::AVIAudioTrack::getAudioStream() const {
return _audStream;
}
diff --git a/video/avi_decoder.h b/video/avi_decoder.h
index f7259bf030..80c11b1e09 100644
--- a/video/avi_decoder.h
+++ b/video/avi_decoder.h
@@ -66,8 +66,13 @@ public:
uint16 getWidth() const { return _header.width; }
uint16 getHeight() const { return _header.height; }
+ bool rewind();
+ bool isRewindable() const { return true; }
+ bool isSeekable() const;
+
protected:
void readNextPacket();
+ bool seekIntern(const Audio::Timestamp &time);
struct BitmapInfoHeader {
uint32 size;
@@ -156,7 +161,7 @@ protected:
class AVIVideoTrack : public FixedRateVideoTrack {
public:
- AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader);
+ AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader, byte *initialPalette = 0);
~AVIVideoTrack();
void decodeFrame(Common::SeekableReadStream *stream);
@@ -169,7 +174,12 @@ protected:
const Graphics::Surface *decodeNextFrame() { return _lastFrame; }
const byte *getPalette() const { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const { return _dirtyPalette; }
- void markPaletteDirty() { _dirtyPalette = true; }
+ void setCurFrame(int frame) { _curFrame = frame; }
+ void loadPaletteFromChunk(Common::SeekableReadStream *chunk);
+ void useInitialPalette();
+
+ bool isRewindable() const { return true; }
+ bool rewind();
protected:
Common::Rational getFrameRate() const { return Common::Rational(_vidsHeader.rate, _vidsHeader.scale); }
@@ -178,6 +188,7 @@ protected:
AVIStreamHeader _vidsHeader;
BitmapInfoHeader _bmInfo;
byte _palette[3 * 256];
+ byte *_initialPalette;
mutable bool _dirtyPalette;
int _frameCount, _curFrame;
@@ -193,6 +204,11 @@ protected:
virtual void queueSound(Common::SeekableReadStream *stream);
Audio::Mixer::SoundType getSoundType() const { return _soundType; }
+ void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime);
+ void resetStream();
+
+ bool isRewindable() const { return true; }
+ bool rewind();
protected:
Audio::AudioStream *getAudioStream() const;
diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp
index a512a49fac..0ab1478727 100644
--- a/video/video_decoder.cpp
+++ b/video/video_decoder.cpp
@@ -62,6 +62,8 @@ void VideoDecoder::close() {
delete *it;
_tracks.clear();
+ _internalTracks.clear();
+ _externalTracks.clear();
_dirtyPalette = false;
_palette = 0;
_startTime = 0;
@@ -340,6 +342,11 @@ bool VideoDecoder::seek(const Audio::Timestamp &time) {
if (!seekIntern(time))
return false;
+ // Seek any external track too
+ for (TrackListIterator it = _externalTracks.begin(); it != _externalTracks.end(); it++)
+ if (!(*it)->seek(time))
+ return false;
+
_lastTimeChange = time;
// Now that we've seeked, start all tracks again
@@ -356,12 +363,12 @@ bool VideoDecoder::seek(const Audio::Timestamp &time) {
}
bool VideoDecoder::seekToFrame(uint frame) {
+ if (!isSeekable())
+ return false;
+
VideoTrack *track = 0;
for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) {
- if (!(*it)->isSeekable())
- return false;
-
if ((*it)->getTrackType() == Track::kTrackTypeVideo) {
// We only allow seeking by frame when one video track
// is present
@@ -472,7 +479,7 @@ Audio::Timestamp VideoDecoder::getDuration() const {
}
bool VideoDecoder::seekIntern(const Audio::Timestamp &time) {
- for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++)
+ for (TrackList::iterator it = _internalTracks.begin(); it != _internalTracks.end(); it++)
if (!(*it)->seek(time))
return false;
@@ -524,10 +531,9 @@ Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getFrameTime(uint frame) con
if (frameRate == frameRate.toInt()) // The nice case (a whole number)
return Audio::Timestamp(0, frame, frameRate.toInt());
- // Just convert to milliseconds.
- Common::Rational time = frame * 1000;
- time /= frameRate;
- return Audio::Timestamp(time.toInt(), 1000);
+ // Convert as best as possible
+ Common::Rational time = frameRate.getInverse() * frame;
+ return Audio::Timestamp(0, time.getNumerator(), time.getDenominator());
}
uint VideoDecoder::FixedRateVideoTrack::getFrameAtTime(const Audio::Timestamp &time) const {
@@ -537,8 +543,10 @@ uint VideoDecoder::FixedRateVideoTrack::getFrameAtTime(const Audio::Timestamp &t
if (frameRate == time.framerate())
return time.totalNumberOfFrames();
- // Default case
- return (time.totalNumberOfFrames() * frameRate / time.framerate()).toInt();
+ // Create the rational based on the time first to hopefully cancel out
+ // *something* when multiplying by the frameRate (which can be large in
+ // some AVI videos).
+ return (Common::Rational(time.totalNumberOfFrames(), time.framerate()) * frameRate).toInt();
}
Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getDuration() const {
@@ -649,9 +657,14 @@ bool VideoDecoder::StreamFileAudioTrack::loadFromFile(const Common::String &base
return _stream != 0;
}
-void VideoDecoder::addTrack(Track *track) {
+void VideoDecoder::addTrack(Track *track, bool isExternal) {
_tracks.push_back(track);
+ if (isExternal)
+ _externalTracks.push_back(track);
+ else
+ _internalTracks.push_back(track);
+
if (track->getTrackType() == Track::kTrackTypeAudio) {
// Update volume settings if it's an audio track
((AudioTrack *)track)->setVolume(_audioVolume);
@@ -681,7 +694,7 @@ bool VideoDecoder::addStreamFileTrack(const Common::String &baseName) {
bool result = track->loadFromFile(baseName);
if (result)
- addTrack(track);
+ addTrack(track, true);
else
delete track;
@@ -712,17 +725,17 @@ void VideoDecoder::setEndTime(const Audio::Timestamp &endTime) {
}
VideoDecoder::Track *VideoDecoder::getTrack(uint track) {
- if (track > _tracks.size())
+ if (track > _internalTracks.size())
return 0;
- return _tracks[track];
+ return _internalTracks[track];
}
const VideoDecoder::Track *VideoDecoder::getTrack(uint track) const {
- if (track > _tracks.size())
+ if (track > _internalTracks.size())
return 0;
- return _tracks[track];
+ return _internalTracks[track];
}
bool VideoDecoder::endOfVideoTracks() const {
diff --git a/video/video_decoder.h b/video/video_decoder.h
index 7811734dd5..ac6586d8dd 100644
--- a/video/video_decoder.h
+++ b/video/video_decoder.h
@@ -594,17 +594,17 @@ protected:
virtual Audio::Timestamp getDuration() const;
Audio::Timestamp getFrameTime(uint frame) const;
- protected:
- /**
- * Get the rate at which this track is played.
- */
- virtual Common::Rational getFrameRate() const = 0;
-
/**
* Get the frame that should be displaying at the given time. This is
* helpful for someone implementing seek().
*/
uint getFrameAtTime(const Audio::Timestamp &time) const;
+
+ protected:
+ /**
+ * Get the rate at which this track is played.
+ */
+ virtual Common::Rational getFrameRate() const = 0;
};
/**
@@ -761,8 +761,11 @@ protected:
* Define a track to be used by this class.
*
* The pointer is then owned by this base class.
+ *
+ * @param track The track to add
+ * @param isExternal Is this an external track not found by loadStream()?
*/
- void addTrack(Track *track);
+ void addTrack(Track *track, bool isExternal = false);
/**
* Whether or not getTime() will sync with a playing audio track.
@@ -814,12 +817,12 @@ protected:
/**
* Get the begin iterator of the tracks
*/
- TrackListIterator getTrackListBegin() { return _tracks.begin(); }
+ TrackListIterator getTrackListBegin() { return _internalTracks.begin(); }
/**
* Get the end iterator of the tracks
*/
- TrackListIterator getTrackListEnd() { return _tracks.end(); }
+ TrackListIterator getTrackListEnd() { return _internalTracks.end(); }
/**
* The internal seek function that does the actual seeking.
@@ -833,6 +836,8 @@ protected:
private:
// Tracks owned by this VideoDecoder
TrackList _tracks;
+ TrackList _internalTracks;
+ TrackList _externalTracks;
// Current playback status
bool _needsUpdate;