Bug 1062733 - Keep track of all TrackBuffer decoders rather than only fully initialized ones. r=cajbir
--- a/content/media/mediasource/TrackBuffer.cpp
+++ b/content/media/mediasource/TrackBuffer.cpp
@@ -86,16 +86,17 @@ TrackBuffer::Shutdown()
mTaskQueue->Shutdown();
mTaskQueue = nullptr;
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
DiscardDecoder();
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
mDecoders[i]->GetReader()->Shutdown();
}
+ mInitializedDecoders.Clear();
NS_DispatchToMainThread(new ReleaseDecoderTask(mDecoders));
MOZ_ASSERT(mDecoders.IsEmpty());
mParentDecoder = nullptr;
}
bool
TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
{
@@ -130,18 +131,19 @@ TrackBuffer::EvictBefore(double aTime)
mCurrentDecoder->GetResource()->EvictBefore(endOffset);
}
MSE_DEBUG("TrackBuffer(%p)::EvictBefore offset=%lld", this, endOffset);
}
double
TrackBuffer::Buffered(dom::TimeRanges* aRanges)
{
+ ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
MOZ_ASSERT(NS_IsMainThread());
- // XXX check default if mDecoders empty?
+
double highestEndTime = 0;
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
mDecoders[i]->GetBuffered(r);
if (r->Length() > 0) {
highestEndTime = std::max(highestEndTime, r->GetEndTime());
aRanges->Union(r);
@@ -158,16 +160,17 @@ TrackBuffer::NewDecoder()
MOZ_ASSERT(!mCurrentDecoder && mParentDecoder);
nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType);
if (!decoder) {
return false;
}
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
mCurrentDecoder = decoder;
+ mDecoders.AppendElement(decoder);
mLastStartTimestamp = 0;
mLastEndTimestamp = UnspecifiedNaN<double>();
mHasInit = true;
return QueueInitializeDecoder(decoder);
}
@@ -201,16 +204,20 @@ TrackBuffer::InitializeDecoder(nsRefPtr<
nsAutoPtr<MetadataTags> tags; // TODO: Handle metadata.
nsresult rv = reader->ReadMetadata(&mi, getter_Transfers(tags));
reader->SetIdle();
if (NS_FAILED(rv) || (!mi.HasVideo() && !mi.HasAudio())) {
// XXX: Need to signal error back to owning SourceBuffer.
MSE_DEBUG("TrackBuffer(%p): Reader %p failed to initialize rv=%x audio=%d video=%d",
this, reader, rv, mi.HasAudio(), mi.HasVideo());
aDecoder->SetTaskQueue(nullptr);
+ {
+ ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+ mDecoders.RemoveElement(aDecoder);
+ }
NS_DispatchToMainThread(new ReleaseDecoderTask(aDecoder));
return;
}
if (mi.HasVideo()) {
MSE_DEBUG("TrackBuffer(%p): Reader %p video resolution=%dx%d",
this, reader, mi.mVideo.mDisplay.width, mi.mVideo.mDisplay.height);
}
@@ -225,24 +232,24 @@ TrackBuffer::InitializeDecoder(nsRefPtr<
void
TrackBuffer::RegisterDecoder(nsRefPtr<SourceBufferDecoder> aDecoder)
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
aDecoder->SetTaskQueue(nullptr);
const MediaInfo& info = aDecoder->GetReader()->GetMediaInfo();
// Initialize the track info since this is the first decoder.
- if (mDecoders.IsEmpty()) {
+ if (mInitializedDecoders.IsEmpty()) {
mHasAudio = info.HasAudio();
mHasVideo = info.HasVideo();
mParentDecoder->OnTrackBufferConfigured(this, info);
} else if ((info.HasAudio() && !mHasAudio) || (info.HasVideo() && !mHasVideo)) {
MSE_DEBUG("TrackBuffer(%p)::RegisterDecoder with mismatched audio/video tracks", this);
}
- mDecoders.AppendElement(aDecoder);
+ mInitializedDecoders.AppendElement(aDecoder);
}
void
TrackBuffer::DiscardDecoder()
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
if (mCurrentDecoder) {
mCurrentDecoder->GetResource()->Ended();
@@ -265,17 +272,17 @@ TrackBuffer::HasInitSegment()
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
return mHasInit;
}
bool
TrackBuffer::IsReady()
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
- MOZ_ASSERT((mHasAudio || mHasVideo) || mDecoders.IsEmpty());
+ MOZ_ASSERT((mHasAudio || mHasVideo) || mInitializedDecoders.IsEmpty());
return HasInitSegment() && (mHasAudio || mHasVideo);
}
void
TrackBuffer::LastTimestamp(double& aStart, double& aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
aStart = mLastStartTimestamp;
@@ -295,45 +302,47 @@ TrackBuffer::SetLastEndTimestamp(double
MOZ_ASSERT(NS_IsMainThread());
mLastEndTimestamp = aEnd;
}
bool
TrackBuffer::ContainsTime(double aTime)
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
- for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+ for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
- mDecoders[i]->GetBuffered(r);
+ mInitializedDecoders[i]->GetBuffered(r);
if (r->Find(aTime) != dom::TimeRanges::NoIndex) {
return true;
}
}
return false;
}
void
TrackBuffer::BreakCycles()
{
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
mDecoders[i]->GetReader()->BreakCycles();
}
- mDecoders.Clear();
+ mInitializedDecoders.Clear();
+ NS_DispatchToMainThread(new ReleaseDecoderTask(mDecoders));
+ MOZ_ASSERT(mDecoders.IsEmpty());
mParentDecoder = nullptr;
}
void
TrackBuffer::ResetDecode()
{
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
mDecoders[i]->GetReader()->ResetDecode();
}
}
const nsTArray<nsRefPtr<SourceBufferDecoder>>&
TrackBuffer::Decoders()
{
// XXX assert OnDecodeThread
- return mDecoders;
+ return mInitializedDecoders;
}
} // namespace mozilla
--- a/content/media/mediasource/TrackBuffer.h
+++ b/content/media/mediasource/TrackBuffer.h
@@ -72,19 +72,19 @@ public:
// contain aTime in their buffered ranges.
bool ContainsTime(double aTime);
void BreakCycles();
// Call ResetDecode() on each decoder in mDecoders.
void ResetDecode();
- // Returns a reference to mDecoders, used by MediaSourceReader to select
- // decoders.
- // TODO: Refactor to a clenaer interface between TrackBuffer and MediaSourceReader.
+ // Returns a reference to mInitializedDecoders, used by MediaSourceReader
+ // to select decoders.
+ // TODO: Refactor to a cleaner interface between TrackBuffer and MediaSourceReader.
const nsTArray<nsRefPtr<SourceBufferDecoder>>& Decoders();
private:
~TrackBuffer();
// Queue execution of InitializeDecoder on mTaskQueue.
bool QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
@@ -97,20 +97,24 @@ private:
// from the decode thread pool.
void RegisterDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
// A task queue using the shared media thread pool. Used exclusively to
// initialize (i.e. call ReadMetadata on) decoders as they are created via
// NewDecoder.
RefPtr<MediaTaskQueue> mTaskQueue;
- // All of the initialized decoders managed by this TrackBuffer. Access
- // protected by mParentDecoder's monitor.
+ // All of the decoders managed by this TrackBuffer. Access protected by
+ // mParentDecoder's monitor.
nsTArray<nsRefPtr<SourceBufferDecoder>> mDecoders;
+ // Contains only the initialized decoders managed by this TrackBuffer.
+ // Access protected by mParentDecoder's monitor.
+ nsTArray<nsRefPtr<SourceBufferDecoder>> mInitializedDecoders;
+
// The decoder that the owning SourceBuffer is currently appending data to.
nsRefPtr<SourceBufferDecoder> mCurrentDecoder;
nsRefPtr<MediaSourceDecoder> mParentDecoder;
const nsCString mType;
// The last start and end timestamps added to the TrackBuffer via
// AppendData. Accessed on the main thread only.
--- a/content/media/mediasource/test/mochitest.ini
+++ b/content/media/mediasource/test/mochitest.ini
@@ -5,8 +5,11 @@ support-files = seek.webm seek.webm^head
[test_MediaSource.html]
skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
[test_SplitAppend.html]
skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
[test_SplitAppendDelay.html]
skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
+
+[test_BufferedSeek.html]
+skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
new file mode 100644
--- /dev/null
+++ b/content/media/mediasource/test/test_BufferedSeek.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>MSE: seeking in buffered range</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ ok(!window.MediaSource, "MediaSource should be hidden behind a pref");
+ var accessThrows = false;
+ try {
+ new MediaSource();
+ } catch (e) {
+ accessThrows = true;
+ }
+ ok(accessThrows, "MediaSource should be hidden behind a pref");
+ SpecialPowers.pushPrefEnv({"set": [[ "media.mediasource.enabled", true ]]},
+ function () {
+ SpecialPowers.setBoolPref("media.mediasource.enabled", true);
+ var ms = new MediaSource();
+ ok(ms, "Create a MediaSource object");
+ ok(ms instanceof EventTarget, "MediaSource must be an EventTarget");
+ is(ms.readyState, "closed", "New MediaSource must be in closed state");
+ // Force wrapper creation, tests for leaks.
+ ms.foo = null;
+ var o = URL.createObjectURL(ms);
+ ok(o, "Create an objectURL from the MediaSource");
+ var v = document.createElement("video");
+ v.preload = "auto";
+ document.body.appendChild(v);
+ v.src = o;
+ ms.addEventListener("sourceopen", function () {
+ ok(true, "Receive a sourceopen event");
+ is(ms.readyState, "open", "MediaSource must be in open state after sourceopen");
+ var sb = ms.addSourceBuffer("video/webm");
+ ok(sb, "Create a SourceBuffer");
+ is(ms.sourceBuffers.length, 1, "MediaSource.sourceBuffers is expected length");
+ is(ms.sourceBuffers[0], sb, "SourceBuffer in list matches our SourceBuffer");
+ fetch("seek.webm", function (blob) {
+ var r = new FileReader();
+ r.addEventListener("load", function (e) {
+ sb.appendBuffer(new Uint8Array(e.target.result));
+ ms.endOfStream();
+ var target = 2;
+ v.addEventListener("loadedmetadata", function () {
+ if (v.buffered.length && v.currentTime != target &&
+ target >= v.buffered.start(0) &&
+ target < v.buffered.end(0)) {
+ v.currentTime = target;
+ }
+ });
+ var wasSeeking = false;
+ v.addEventListener("seeking", function () {
+ wasSeeking = true;
+ is(v.currentTime, target, "Video currentTime not at target");
+ });
+ v.addEventListener("seeked", function () {
+ ok(wasSeeking, "Received expected seeking and seeked events");
+ is(v.currentTime, target, "Video currentTime not at target");
+ v.parentNode.removeChild(v);
+ SimpleTest.finish();
+ });
+ });
+ r.readAsArrayBuffer(blob);
+ });
+ });
+ ms.addEventListener("sourceended", function () {
+ ok(true, "Receive a sourceended event");
+ is(ms.readyState, "ended", "MediaSource must be in ended state after sourceended");
+ });
+ });
+});
+
+function fetch(src, cb) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", src, true);
+ xhr.responseType = "blob";
+ xhr.addEventListener("load", function (e) {
+ if (xhr.status != 200) {
+ return false;
+ }
+ cb(xhr.response);
+ });
+ xhr.send();
+};
+</script>
+</pre>
+</body>
+</html>
--- a/content/media/mediasource/test/test_MediaSource.html
+++ b/content/media/mediasource/test/test_MediaSource.html
@@ -1,12 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
- <title>Test whether we can create an MediaSource interface</title>
+ <title>MSE: basic functionality</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
--- a/content/media/mediasource/test/test_SplitAppend.html
+++ b/content/media/mediasource/test/test_SplitAppend.html
@@ -1,12 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
- <title>Test whether we can create an MediaSource interface</title>
+ <title>MSE: append initialization and media segment separately</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
--- a/content/media/mediasource/test/test_SplitAppendDelay.html
+++ b/content/media/mediasource/test/test_SplitAppendDelay.html
@@ -1,12 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
- <title>Test whether we can create an MediaSource interface</title>
+ <title>MSE: append segments with delay</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();