Bug 1044498 - Handle overlapped SourceBuffer appends by spinning up a new decoder. Only implemented for WebM so far. r=cajbir

This commit is contained in:
Matthew Gregan 2014-08-19 17:13:27 +12:00
parent ff3baa6107
commit bc89b03a62
5 changed files with 95 additions and 44 deletions

View File

@ -210,7 +210,7 @@ MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
nsRefPtr<SourceBuffer> sourceBuffer = SourceBuffer::Create(this, NS_ConvertUTF16toUTF8(mimeType));
nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, NS_ConvertUTF16toUTF8(mimeType));
if (!sourceBuffer) {
aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
return nullptr;

View File

@ -26,6 +26,8 @@
#include "SourceBufferDecoder.h"
#include "mozilla/Preferences.h"
#include "WebMBufferedParser.h"
struct JSContext;
class JSObject;
@ -59,11 +61,21 @@ public:
return false;
}
virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
double& aStart, double& aEnd)
{
return false;
}
static ContainerParser* CreateForMIMEType(const nsACString& aType);
};
class WebMContainerParser : public ContainerParser {
public:
WebMContainerParser()
: mTimecodeScale(0)
{}
bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
ContainerParser::IsInitSegmentPresent(aData, aLength);
@ -83,6 +95,37 @@ public:
}
return false;
}
virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
double& aStart, double& aEnd)
{
// XXX: This is overly primitive, needs to collect data as it's appended
// to the SB and handle, rather than assuming everything is present in a
// single aData segment.
WebMBufferedParser parser(0);
if (mTimecodeScale != 0) {
parser.SetTimecodeScale(mTimecodeScale);
}
nsTArray<WebMTimeDataOffset> mapping;
ReentrantMonitor dummy("dummy");
parser.Append(aData, aLength, mapping, dummy);
mTimecodeScale = parser.GetTimecodeScale();
if (mapping.IsEmpty()) {
return false;
}
static const double NS_PER_S = 1e9;
aStart = mapping[0].mTimecode / NS_PER_S;
aEnd = mapping.LastElement().mTimecode / NS_PER_S;
return true;
}
private:
uint32_t mTimecodeScale;
};
class MP4ContainerParser : public ContainerParser {
@ -352,6 +395,7 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
: DOMEventTargetHelper(aMediaSource->GetParentObject())
, mMediaSource(aMediaSource)
, mType(aType)
, mLastParsedTimestamp(UnspecifiedNaN<double>())
, mAppendWindowStart(0)
, mAppendWindowEnd(PositiveInfinity<double>())
, mTimestampOffset(0)
@ -366,13 +410,6 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
InitNewDecoder();
}
already_AddRefed<SourceBuffer>
SourceBuffer::Create(MediaSource* aMediaSource, const nsACString& aType)
{
nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(aMediaSource, aType);
return sourceBuffer.forget();
}
SourceBuffer::~SourceBuffer()
{
MOZ_ASSERT(NS_IsMainThread());
@ -504,6 +541,26 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
aRv.Throw(NS_ERROR_FAILURE);
return;
}
double start, end;
if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
if (start <= mLastParsedTimestamp) {
// This data is earlier in the timeline than data we have already
// processed, so we must create a new decoder to handle the decoding.
DiscardDecoder();
// If we've got a decoder here, it's not initialized, so we can use it
// rather than creating a new one.
if (!InitNewDecoder()) {
aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
return;
}
MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized (%f, %f).",
this, start, end);
mDecoderInitialized = true;
}
mLastParsedTimestamp = end;
MSE_DEBUG("SourceBuffer(%p)::AppendData: Segment start=%f end=%f", this, start, end);
}
// XXX: For future reference: NDA call must run on the main thread.
mDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
aLength,
@ -520,7 +577,7 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
const uint32_t evict_threshold = 75 * (1 << 20);
bool evicted = mDecoder->GetResource()->EvictData(evict_threshold);
if (evicted) {
MSE_DEBUG("SourceBuffer(%p)::AppendBuffer Evict; current buffered start=%f",
MSE_DEBUG("SourceBuffer(%p)::AppendData Evict; current buffered start=%f",
this, GetBufferedStart());
// We notify that we've evicted from the time range 0 through to

View File

@ -89,7 +89,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SourceBuffer, DOMEventTargetHelper)
static already_AddRefed<SourceBuffer> Create(MediaSource* aMediaSource, const nsACString& aType);
SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
MediaSource* GetParentObject() const;
@ -114,8 +114,6 @@ public:
private:
~SourceBuffer();
SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
friend class AsyncEventRunner<SourceBuffer>;
void DispatchSimpleEvent(const char* aName);
void QueueAsyncSimpleEvent(const char* aName);
@ -141,6 +139,8 @@ private:
nsAutoPtr<ContainerParser> mParser;
double mLastParsedTimestamp;
nsRefPtr<SourceBufferDecoder> mDecoder;
nsTArray<nsRefPtr<SourceBufferDecoder>> mDecoders;

View File

@ -1026,52 +1026,45 @@ nsresult WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
nsresult WebMReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime)
{
MediaResource* resource = mDecoder->GetResource();
uint64_t timecodeScale;
if (!mContext || nestegg_tstamp_scale(mContext, &timecodeScale) == -1) {
return NS_OK;
if (aBuffered->Length() != 0) {
return NS_ERROR_FAILURE;
}
MediaResource* resource = mDecoder->GetResource();
// Special case completely cached files. This also handles local files.
bool isFullyCached = resource->IsDataCachedToEndOfResource(0);
if (isFullyCached) {
if (mContext && resource->IsDataCachedToEndOfResource(0)) {
uint64_t duration = 0;
if (nestegg_duration(mContext, &duration) == 0) {
aBuffered->Add(0, duration / NS_PER_S);
return NS_OK;
}
}
uint32_t bufferedLength = 0;
aBuffered->GetLength(&bufferedLength);
// Either we the file is not fully cached, or we couldn't find a duration in
// the WebM bitstream.
if (!isFullyCached || !bufferedLength) {
MediaResource* resource = mDecoder->GetResource();
nsTArray<MediaByteRange> ranges;
nsresult res = resource->GetCachedRanges(ranges);
NS_ENSURE_SUCCESS(res, res);
nsTArray<MediaByteRange> ranges;
nsresult res = resource->GetCachedRanges(ranges);
NS_ENSURE_SUCCESS(res, res);
for (uint32_t index = 0; index < ranges.Length(); index++) {
uint64_t start, end;
bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart,
ranges[index].mEnd,
&start, &end);
if (rv) {
double startTime = start * timecodeScale / NS_PER_S - aStartTime;
double endTime = end * timecodeScale / NS_PER_S - aStartTime;
// If this range extends to the end of the file, the true end time
// is the file's duration.
if (resource->IsDataCachedToEndOfResource(ranges[index].mStart)) {
uint64_t duration = 0;
if (nestegg_duration(mContext, &duration) == 0) {
endTime = duration / NS_PER_S;
}
for (uint32_t index = 0; index < ranges.Length(); index++) {
uint64_t start, end;
bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart,
ranges[index].mEnd,
&start, &end);
if (rv) {
double startTime = start / NS_PER_S - aStartTime;
double endTime = end / NS_PER_S - aStartTime;
// If this range extends to the end of the file, the true end time
// is the file's duration.
if (mContext && resource->IsDataCachedToEndOfResource(ranges[index].mStart)) {
uint64_t duration = 0;
if (nestegg_duration(mContext, &duration) == 0) {
endTime = duration / NS_PER_S;
}
aBuffered->Add(startTime, endTime);
}
aBuffered->Add(startTime, endTime);
}
}

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS += [
'WebMBufferedParser.h',
'WebMDecoder.h',
'WebMReader.h',
]