diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
index 9dc401a005..80faedcda3 100644
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -557,6 +557,14 @@ HTMLMediaElement::GetMozDebugReaderData(nsAString& aString)
}
}
+void
+HTMLMediaElement::MozDumpDebugInfo()
+{
+ if (mDecoder) {
+ mDecoder->DumpDebugInfo();
+ }
+}
+
already_AddRefed
HTMLMediaElement::GetSrcObject() const
{
diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h
index 5f69aa95b9..c114270e6e 100644
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -611,6 +611,8 @@ public:
// data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);
+ void MozDumpDebugInfo();
+
already_AddRefed GetSrcObject() const;
void SetSrcObject(DOMMediaStream& aValue);
void SetSrcObject(DOMMediaStream* aValue);
diff --git a/dom/media/MediaCache.cpp b/dom/media/MediaCache.cpp
index 0667265ce7..6582113738 100644
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -1975,6 +1975,9 @@ MediaCacheStream::Close()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ if (!mInitialized)
+ return;
+
if (gMediaCache) {
ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
CloseInternal(mon);
diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp
index cef367f76a..03174335a1 100644
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -51,11 +51,15 @@ static const uint64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
// avoid redefined macro in unified build
#undef DECODER_LOG
+#undef DUMP_LOG
LazyLogModule gMediaDecoderLog("MediaDecoder");
#define DECODER_LOG(x, ...) \
MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("Decoder=%p " x, this, ##__VA_ARGS__))
+#define DUMP_LOG(x, ...) \
+ NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString("Decoder=%p " x, this, ##__VA_ARGS__).get(), nullptr, nullptr, -1)
+
static const char*
ToPlayStateStr(MediaDecoder::PlayState aState)
{
@@ -1876,6 +1880,23 @@ MediaDecoder::NextFrameBufferedStatus()
: MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
}
+void
+MediaDecoder::DumpDebugInfo()
+{
+ DUMP_LOG("metadata: channels=%u rate=%u hasAudio=%d hasVideo=%d, "
+ "state: mPlayState=%s mIsDormant=%d, mShuttingDown=%d",
+ mInfo->mAudio.mChannels, mInfo->mAudio.mRate, mInfo->HasAudio(), mInfo->HasVideo(),
+ PlayStateStr(), mIsDormant, mShuttingDown);
+
+ nsString str;
+ GetMozDebugReaderData(str);
+ DUMP_LOG("reader data:\n%s", NS_ConvertUTF16toUTF8(str).get());
+
+ if (!mShuttingDown && GetStateMachine()) {
+ GetStateMachine()->DumpDebugInfo();
+ }
+}
+
void
MediaDecoder::NotifyAudibleStateChanged()
{
diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h
index 23ce9b3c32..a40f7d3844 100644
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -3,184 +3,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/*
-Each video element based on MediaDecoder has a state machine to manage
-its play state and keep the current frame up to date. All state machines
-share time in a single shared thread. Each decoder also has a TaskQueue
-running in a SharedThreadPool to decode audio and video data.
-Each decoder also has a thread to push decoded audio
-to the hardware. This thread is not created until playback starts, but
-currently is not destroyed when paused, only when playback ends.
-The decoder owns the resources for downloading the media file, and the
-high level state. It holds an owning reference to the state machine that
-owns all the resources related to decoding data, and manages the low level
-decoding operations and A/V sync.
-
-Each state machine runs on the shared state machine thread. Every time some
-action is required for a state machine, it is scheduled to run on the shared
-the state machine thread. The state machine runs one "cycle" on the state
-machine thread, and then returns. If necessary, it will schedule itself to
-run again in future. While running this cycle, it must not block the
-thread, as other state machines' events may need to run. State shared
-between a state machine's threads is synchronised via the monitor owned
-by its MediaDecoder object.
-
-The Main thread controls the decode state machine by setting the value
-of a mPlayState variable and notifying on the monitor based on the
-high level player actions required (Seek, Pause, Play, etc).
-
-The player states are the states requested by the client through the
-DOM API. They represent the desired state of the player, while the
-decoder's state represents the actual state of the decoder.
-
-The high level state of the player is maintained via a PlayState value.
-It can have the following states:
-
-START
- The decoder has been initialized but has no resource loaded.
-PAUSED
- A request via the API has been received to pause playback.
-LOADING
- A request via the API has been received to load a resource.
-PLAYING
- A request via the API has been received to start playback.
-SEEKING
- A request via the API has been received to start seeking.
-COMPLETED
- Playback has completed.
-SHUTDOWN
- The decoder is about to be destroyed.
-
-State transition occurs when the Media Element calls the Play, Seek,
-etc methods on the MediaDecoder object. When the transition
-occurs MediaDecoder then calls the methods on the decoder state
-machine object to cause it to behave as required by the play state.
-State transitions will likely schedule the state machine to run to
-affect the change.
-
-An implementation of the MediaDecoderStateMachine class is the event
-that gets dispatched to the state machine thread. Each time the event is run,
-the state machine must cycle the state machine once, and then return.
-
-The state machine has the following states:
-
-DECODING_METADATA
- The media headers are being loaded, and things like framerate, etc are
- being determined.
-DECODING_FIRSTFRAME
- The first frame of audio/video data is being decoded.
-DECODING
- The decode has started. If the PlayState is PLAYING, the decode thread
- should be alive and decoding video and audio frame, the audio thread
- should be playing audio, and the state machine should run periodically
- to update the video frames being displayed.
-SEEKING
- A seek operation is in progress. The decode thread should be seeking.
-BUFFERING
- Decoding is paused while data is buffered for smooth playback. If playback
- is paused (PlayState transitions to PAUSED) we'll destory the decode thread.
-COMPLETED
- The resource has completed decoding, but possibly not finished playback.
- The decode thread will be destroyed. Once playback finished, the audio
- thread will also be destroyed.
-SHUTDOWN
- The decoder object and its state machine are about to be destroyed.
- Once the last state machine has been destroyed, the shared state machine
- thread will also be destroyed. It will be recreated later if needed.
-
-The following result in state transitions.
-
-Shutdown()
- Clean up any resources the MediaDecoderStateMachine owns.
-Play()
- Start decoding and playback of media data.
-Buffer
- This is not user initiated. It occurs when the
- available data in the stream drops below a certain point.
-Complete
- This is not user initiated. It occurs when the
- stream is completely decoded.
-Seek(double)
- Seek to the time position given in the resource.
-
-A state transition diagram:
-
- |---<-- DECODING_METADATA ----->--------|
- | | |
- Seek(t) v Shutdown()
- | | |
- -->--- DECODING_FIRSTFRAME |------->-----------------|
- | | |
- | Shutdown() |
- | | |
- v |-->----------------->--------------------------|
- |---------------->----->------------------------| v
- DECODING | | | | |
- ^ v Seek(t) | | | |
- | Play() | v | | |
- ^-----------<----SEEKING | v Complete v v
- | | | | | |
- | | | COMPLETED SHUTDOWN-<-|
- ^ ^ | |Shutdown() |
- | | | >-------->-----^
- | Play() |Seek(t) |Buffer() |
- -----------<--------<-------BUFFERING |
- | ^
- v Shutdown() |
- | |
- ------------>-----|
-
-The following represents the states that the MediaDecoder object
-can be in, and the valid states the MediaDecoderStateMachine can be in at that
-time:
-
-player LOADING decoder DECODING_METADATA, DECODING_FIRSTFRAME
-player PLAYING decoder DECODING, BUFFERING, SEEKING, COMPLETED
-player PAUSED decoder DECODING, BUFFERING, SEEKING, COMPLETED
-player SEEKING decoder SEEKING
-player COMPLETED decoder SHUTDOWN
-player SHUTDOWN decoder SHUTDOWN
-
-The general sequence of events is:
-
-1) The video element calls Load on MediaDecoder. This creates the
- state machine and starts the channel for downloading the
- file. It instantiates and schedules the MediaDecoderStateMachine. The
- high level LOADING state is entered, which results in the decode
- thread being created and starting to decode metadata. These are
- the headers that give the video size, framerate, etc. Load() returns
- immediately to the calling video element.
-
-2) When the metadata has been loaded by the decode thread, the state machine
- will call a method on the video element object to inform it that this
- step is done, so it can do the things required by the video specification
- at this stage. The decode thread then continues to decode the first frame
- of data.
-
-3) When the first frame of data has been successfully decoded the state
- machine calls a method on the video element object to inform it that
- this step has been done, once again so it can do the required things
- by the video specification at this stage.
-
- This results in the high level state changing to PLAYING or PAUSED
- depending on any user action that may have occurred.
-
- While the play state is PLAYING, the decode thread will decode
- data, and the audio thread will push audio data to the hardware to
- be played. The state machine will run periodically on the shared
- state machine thread to ensure video frames are played at the
- correct time; i.e. the state machine manages A/V sync.
-
-The Shutdown method on MediaDecoder closes the download channel, and
-signals to the state machine that it should shutdown. The state machine
-shuts down asynchronously, and will release the owning reference to the
-state machine once its threads are shutdown.
-
-The owning object of a MediaDecoder object *MUST* call Shutdown when
-destroying the MediaDecoder object.
-
-*/
#if !defined(MediaDecoder_h_)
#define MediaDecoder_h_
@@ -676,6 +499,8 @@ private:
// data. Used for debugging purposes.
virtual void GetMozDebugReaderData(nsAString& aString) {}
+ virtual void DumpDebugInfo();
+
protected:
virtual ~MediaDecoder();
diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp
index 8a07186e4e..6989b3daf3 100644
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -63,6 +63,7 @@ using namespace mozilla::media;
#undef LOG
#undef DECODER_LOG
#undef VERBOSE_LOG
+#undef DUMP_LOG
#define LOG(m, l, x, ...) \
MOZ_LOG(m, l, ("Decoder=%p " x, mDecoderID, ##__VA_ARGS__))
@@ -79,6 +80,9 @@ using namespace mozilla::media;
#define DECODER_WARN(x, ...) \
DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get()))
+#define DUMP_LOG(x, ...) \
+ NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get(), nullptr, nullptr, -1)
+
// Certain constants get stored as member variables and then adjusted by various
// scale factors on a per-decoder basis. We want to make sure to avoid using these
// constants directly, so we put them in a namespace.
@@ -2867,6 +2871,28 @@ uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const
: std::max(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
}
+void
+MediaDecoderStateMachine::DumpDebugInfo()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // It is fine to capture a raw pointer here because MediaDecoder only call
+ // this function before shutdown begins.
+ nsCOMPtr r = NS_NewRunnableFunction([this] () {
+ DUMP_LOG(
+ "GetMediaTime=%lld GetClock=%lld "
+ "mState=%s mPlayState=%d mDecodingFirstFrame=%d IsPlaying=%d "
+ "mAudioStatus=%s mVideoStatus=%s mDecodedAudioEndTime=%lld mDecodedVideoEndTime=%lld "
+ "mIsAudioPrerolling=%d mIsVideoPrerolling=%d",
+ GetMediaTime(), mMediaSink->IsStarted() ? GetClock() : -1,
+ gMachineStateStr[mState], mPlayState.Ref(), mDecodingFirstFrame, IsPlaying(),
+ AudioRequestStatus(), VideoRequestStatus(), mDecodedAudioEndTime, mDecodedVideoEndTime,
+ mIsAudioPrerolling, mIsVideoPrerolling);
+ });
+
+ OwnerThread()->DispatchStateChange(r.forget());
+}
+
void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
bool aFinishWhenEnded)
{
diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h
index 0954219757..b9ca7647f0 100644
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -157,6 +157,8 @@ public:
DECODER_STATE_ERROR
};
+ void DumpDebugInfo();
+
void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
// Remove an output stream added with AddOutputStream.
void RemoveOutputStream(MediaStream* aStream);
diff --git a/dom/media/mediasource/test/mediasource.js b/dom/media/mediasource/test/mediasource.js
index bd25e19ba8..f65b6943f1 100644
--- a/dom/media/mediasource/test/mediasource.js
+++ b/dom/media/mediasource/test/mediasource.js
@@ -18,7 +18,8 @@ function runWithMSE(testFunction) {
addLoadEvent(function () {
SpecialPowers.pushPrefEnv({"set": [
- [ "media.mediasource.enabled", true ],
+ [ "media.mediasource.enabled", true ],
+ [ "media.test.dumpDebugInfo", true ],
]},
bootstrapTest);
});
@@ -105,3 +106,13 @@ function fetchAndLoad(sb, prefix, chunks, suffix) {
return rv;
});
}
+
+//Register timeout function to dump debugging logs.
+SimpleTest.registerTimeoutFunction(function() {
+ for (var v of document.getElementsByTagName("video")) {
+ v.mozDumpDebugInfo();
+ }
+ for (var a of document.getElementsByTagName("audio")) {
+ a.mozDumpDebugInfo();
+ }
+});
\ No newline at end of file
diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js
index 7286e89207..8645d3a92c 100644
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -797,7 +797,8 @@ var PARALLEL_TESTS = 2;
var gTestPrefs = [
['media.recorder.max_memory', 1024],
["media.preload.default", 2], // default preload = metadata
- ["media.preload.auto", 3] // auto preload = enough
+ ["media.preload.auto", 3], // auto preload = enough
+ ["media.test.dumpDebugInfo", true],
];
// When true, we'll loop forever on whatever test we run. Use this to debug
@@ -962,4 +963,14 @@ function isSlowPlatform() {
// like file_access_controls.html.
if ("SimpleTest" in window) {
SimpleTest.requestFlakyTimeout("untriaged");
+
+ // Register timeout function to dump debugging logs.
+ SimpleTest.registerTimeoutFunction(function() {
+ for (var v of document.getElementsByTagName("video")) {
+ v.mozDumpDebugInfo();
+ }
+ for (var a of document.getElementsByTagName("audio")) {
+ a.mozDumpDebugInfo();
+ }
+ });
}
diff --git a/dom/webidl/HTMLMediaElement.webidl b/dom/webidl/HTMLMediaElement.webidl
index 82c3446ab0..0385c6a01a 100644
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -107,6 +107,9 @@ partial interface HTMLMediaElement {
[ChromeOnly]
readonly attribute DOMString mozDebugReaderData;
+ [Pref="media.test.dumpDebugInfo"]
+ void mozDumpDebugInfo();
+
attribute MediaStream? srcObject;
// TODO: remove prefixed version soon (1183495).
attribute MediaStream? mozSrcObject;
diff --git a/toolkit/devtools/debugger/utils.js b/toolkit/devtools/debugger/utils.js
index 5cf1dbd1ba..31f4092cc3 100644
--- a/toolkit/devtools/debugger/utils.js
+++ b/toolkit/devtools/debugger/utils.js
@@ -106,7 +106,7 @@ const SourceUtils = {
clearCache: function() {
this._labelsCache.clear();
this._groupsCache.clear();
- this._minifiedCache = new WeakMap();
+ this._minifiedCache.clear();
},
/**
@@ -304,7 +304,11 @@ const SourceUtils = {
}
// Prepend the hostname and port number.
if (aSeq == 4) {
- let host = aUrl.hostPort;
+ let host;
+ try {
+ // Bug 1261860: jar: URLs throw when accessing `hostPost`
+ host = aUrl.hostPort;
+ } catch(e) {}
if (host) {
return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
}