summaryrefslogtreecommitdiffstats
path: root/dom/media/MediaResource.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/MediaResource.h')
-rw-r--r--dom/media/MediaResource.h820
1 files changed, 820 insertions, 0 deletions
diff --git a/dom/media/MediaResource.h b/dom/media/MediaResource.h
new file mode 100644
index 000000000..7d2f153aa
--- /dev/null
+++ b/dom/media/MediaResource.h
@@ -0,0 +1,820 @@
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#if !defined(MediaResource_h_)
+#define MediaResource_h_
+
+#include "mozilla/Mutex.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsISeekableStream.h"
+#include "nsIStreamingProtocolController.h"
+#include "nsIStreamListener.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "Intervals.h"
+#include "MediaCache.h"
+#include "MediaData.h"
+#include "MediaResourceCallback.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+// For HTTP seeking, if number of bytes needing to be
+// seeked forward is less than this value then a read is
+// done rather than a byte range request.
+//
+// If we assume a 100Mbit connection, and assume reissuing an HTTP seek causes
+// a delay of 200ms, then in that 200ms we could have simply read ahead 2MB. So
+// setting SEEK_VS_READ_THRESHOLD to 1MB sounds reasonable.
+static const int64_t SEEK_VS_READ_THRESHOLD = 1 * 1024 * 1024;
+
+static const uint32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE = 416;
+
+// Number of bytes we have accumulated before we assume the connection download
+// rate can be reliably calculated. 57 Segments at IW=3 allows slow start to
+// reach a CWND of 30 (See bug 831998)
+static const int64_t RELIABLE_DATA_THRESHOLD = 57 * 1460;
+
+class nsIHttpChannel;
+class nsIPrincipal;
+
+namespace mozilla {
+
+class MediaChannelStatistics;
+
+/**
+ * This class is useful for estimating rates of data passing through
+ * some channel. The idea is that activity on the channel "starts"
+ * and "stops" over time. At certain times data passes through the
+ * channel (usually while the channel is active; data passing through
+ * an inactive channel is ignored). The GetRate() function computes
+ * an estimate of the "current rate" of the channel, which is some
+ * kind of average of the data passing through over the time the
+ * channel is active.
+ *
+ * All methods take "now" as a parameter so the user of this class can
+ * control the timeline used.
+ */
+class MediaChannelStatistics {
+public:
+ MediaChannelStatistics()
+ : mAccumulatedBytes(0)
+ , mIsStarted(false)
+ {
+ Reset();
+ }
+
+ explicit MediaChannelStatistics(MediaChannelStatistics * aCopyFrom)
+ {
+ MOZ_ASSERT(aCopyFrom);
+ mAccumulatedBytes = aCopyFrom->mAccumulatedBytes;
+ mAccumulatedTime = aCopyFrom->mAccumulatedTime;
+ mLastStartTime = aCopyFrom->mLastStartTime;
+ mIsStarted = aCopyFrom->mIsStarted;
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaChannelStatistics)
+
+ void Reset() {
+ mLastStartTime = TimeStamp();
+ mAccumulatedTime = TimeDuration(0);
+ mAccumulatedBytes = 0;
+ mIsStarted = false;
+ }
+ void Start() {
+ if (mIsStarted)
+ return;
+ mLastStartTime = TimeStamp::Now();
+ mIsStarted = true;
+ }
+ void Stop() {
+ if (!mIsStarted)
+ return;
+ mAccumulatedTime += TimeStamp::Now() - mLastStartTime;
+ mIsStarted = false;
+ }
+ void AddBytes(int64_t aBytes) {
+ if (!mIsStarted) {
+ // ignore this data, it may be related to seeking or some other
+ // operation we don't care about
+ return;
+ }
+ mAccumulatedBytes += aBytes;
+ }
+ double GetRateAtLastStop(bool* aReliable) {
+ double seconds = mAccumulatedTime.ToSeconds();
+ *aReliable = (seconds >= 1.0) ||
+ (mAccumulatedBytes >= RELIABLE_DATA_THRESHOLD);
+ if (seconds <= 0.0)
+ return 0.0;
+ return static_cast<double>(mAccumulatedBytes)/seconds;
+ }
+ double GetRate(bool* aReliable) {
+ TimeDuration time = mAccumulatedTime;
+ if (mIsStarted) {
+ time += TimeStamp::Now() - mLastStartTime;
+ }
+ double seconds = time.ToSeconds();
+ *aReliable = (seconds >= 3.0) ||
+ (mAccumulatedBytes >= RELIABLE_DATA_THRESHOLD);
+ if (seconds <= 0.0)
+ return 0.0;
+ return static_cast<double>(mAccumulatedBytes)/seconds;
+ }
+private:
+ ~MediaChannelStatistics() {}
+ int64_t mAccumulatedBytes;
+ TimeDuration mAccumulatedTime;
+ TimeStamp mLastStartTime;
+ bool mIsStarted;
+};
+
+// Represents a section of contiguous media, with a start and end offset.
+// Used to denote ranges of data which are cached.
+
+typedef media::Interval<int64_t> MediaByteRange;
+typedef media::IntervalSet<int64_t> MediaByteRangeSet;
+
+/**
+ * Provides a thread-safe, seek/read interface to resources
+ * loaded from a URI. Uses MediaCache to cache data received over
+ * Necko's async channel API, thus resolving the mismatch between clients
+ * that need efficient random access to the data and protocols that do not
+ * support efficient random access, such as HTTP.
+ *
+ * Instances of this class must be created on the main thread.
+ * Most methods must be called on the main thread only. Read, Seek and
+ * Tell must only be called on non-main threads. In the case of the Ogg
+ * Decoder they are called on the Decode thread for example. You must
+ * ensure that no threads are calling these methods once Close is called.
+ *
+ * Instances of this class are reference counted. Use nsRefPtr for
+ * managing the lifetime of instances of this class.
+ *
+ * The generic implementation of this class is ChannelMediaResource, which can
+ * handle any URI for which Necko supports AsyncOpen.
+ * The 'file:' protocol can be implemented efficiently with direct random
+ * access, so the FileMediaResource implementation class bypasses the cache.
+ * MediaResource::Create automatically chooses the best implementation class.
+ */
+class MediaResource : public nsISupports
+{
+public:
+ // Our refcounting is threadsafe, and when our refcount drops to zero
+ // we dispatch an event to the main thread to delete the MediaResource.
+ // Note that this means it's safe for references to this object to be
+ // released on a non main thread, but the destructor will always run on
+ // the main thread.
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // The following can be called on the main thread only:
+ // Get the URI
+ virtual nsIURI* URI() const { return nullptr; }
+ // Close the resource, stop any listeners, channels, etc.
+ // Cancels any currently blocking Read request and forces that request to
+ // return an error.
+ virtual nsresult Close() = 0;
+ // Suspend any downloads that are in progress.
+ // If aCloseImmediately is set, resources should be released immediately
+ // since we don't expect to resume again any time soon. Otherwise we
+ // may resume again soon so resources should be held for a little
+ // while.
+ virtual void Suspend(bool aCloseImmediately) = 0;
+ // Resume any downloads that have been suspended.
+ virtual void Resume() = 0;
+ // Get the current principal for the channel
+ virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
+ // If this returns false, then we shouldn't try to clone this MediaResource
+ // because its underlying resources are not suitable for reuse (e.g.
+ // because the underlying connection has been lost, or this resource
+ // just can't be safely cloned). If this returns true, CloneData could
+ // still fail. If this returns false, CloneData should not be called.
+ virtual bool CanClone() { return false; }
+ // Create a new stream of the same type that refers to the same URI
+ // with a new channel. Any cached data associated with the original
+ // stream should be accessible in the new stream too.
+ virtual already_AddRefed<MediaResource> CloneData(MediaResourceCallback* aCallback) = 0;
+ // Set statistics to be recorded to the object passed in.
+ virtual void RecordStatisticsTo(MediaChannelStatistics *aStatistics) { }
+
+ // These methods are called off the main thread.
+ // The mode is initially MODE_PLAYBACK.
+ virtual void SetReadMode(MediaCacheStream::ReadMode aMode) = 0;
+ // This is the client's estimate of the playback rate assuming
+ // the media plays continuously. The cache can't guess this itself
+ // because it doesn't know when the decoder was paused, buffering, etc.
+ virtual void SetPlaybackRate(uint32_t aBytesPerSecond) = 0;
+ // Read up to aCount bytes from the stream. The read starts at
+ // aOffset in the stream, seeking to that location initially if
+ // it is not the current stream offset. The remaining arguments,
+ // results and requirements are the same as per the Read method.
+ virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
+ uint32_t aCount, uint32_t* aBytes) = 0;
+ // This method returns nullptr if anything fails.
+ // Otherwise, it returns an owned buffer.
+ // MediaReadAt may return fewer bytes than requested if end of stream is
+ // encountered. There is no need to call it again to get more data.
+ virtual already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount)
+ {
+ RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
+ bool ok = bytes->SetLength(aCount, fallible);
+ NS_ENSURE_TRUE(ok, nullptr);
+ char* curr = reinterpret_cast<char*>(bytes->Elements());
+ const char* start = curr;
+ while (aCount > 0) {
+ uint32_t bytesRead;
+ nsresult rv = ReadAt(aOffset, curr, aCount, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ if (!bytesRead) {
+ break;
+ }
+ aOffset += bytesRead;
+ aCount -= bytesRead;
+ curr += bytesRead;
+ }
+ bytes->SetLength(curr - start);
+ return bytes.forget();
+ }
+
+ // Report the current offset in bytes from the start of the stream.
+ // This is used to approximate where we currently are in the playback of a
+ // media.
+ // A call to ReadAt will update this position.
+ virtual int64_t Tell() = 0;
+ // Moves any existing channel loads into or out of background. Background
+ // loads don't block the load event. This also determines whether or not any
+ // new loads initiated (for example to seek) will be in the background.
+ virtual void SetLoadInBackground(bool aLoadInBackground) {}
+ // Ensures that the value returned by IsSuspendedByCache below is up to date
+ // (i.e. the cache has examined this stream at least once).
+ virtual void EnsureCacheUpToDate() {}
+
+ // These can be called on any thread.
+ // Cached blocks associated with this stream will not be evicted
+ // while the stream is pinned.
+ virtual void Pin() = 0;
+ virtual void Unpin() = 0;
+ // Get the estimated download rate in bytes per second (assuming no
+ // pausing of the channel is requested by Gecko).
+ // *aIsReliable is set to true if we think the estimate is useful.
+ virtual double GetDownloadRate(bool* aIsReliable) = 0;
+ // Get the length of the stream in bytes. Returns -1 if not known.
+ // This can change over time; after a seek operation, a misbehaving
+ // server may give us a resource of a different length to what it had
+ // reported previously --- or it may just lie in its Content-Length
+ // header and give us more or less data than it reported. We will adjust
+ // the result of GetLength to reflect the data that's actually arriving.
+ virtual int64_t GetLength() = 0;
+ // Returns the offset of the first byte of cached data at or after aOffset,
+ // or -1 if there is no such cached data.
+ virtual int64_t GetNextCachedData(int64_t aOffset) = 0;
+ // Returns the end of the bytes starting at the given offset
+ // which are in cache.
+ virtual int64_t GetCachedDataEnd(int64_t aOffset) = 0;
+ // Returns true if all the data from aOffset to the end of the stream
+ // is in cache. If the end of the stream is not known, we return false.
+ virtual bool IsDataCachedToEndOfResource(int64_t aOffset) = 0;
+ // Returns true if we are expecting any more data to arrive
+ // sometime in the not-too-distant future, either from the network or from
+ // an appendBuffer call on a MediaSource element.
+ virtual bool IsExpectingMoreData()
+ {
+ // MediaDecoder::mDecoderPosition is roughly the same as Tell() which
+ // returns a position updated by latest Read() or ReadAt().
+ return !IsDataCachedToEndOfResource(Tell()) && !IsSuspended();
+ }
+ // Returns true if this stream is suspended by the cache because the
+ // cache is full. If true then the decoder should try to start consuming
+ // data, otherwise we may not be able to make progress.
+ // MediaDecoder::NotifySuspendedStatusChanged is called when this
+ // changes.
+ // For resources using the media cache, this returns true only when all
+ // streams for the same resource are all suspended.
+ virtual bool IsSuspendedByCache() = 0;
+ // Returns true if this stream has been suspended.
+ virtual bool IsSuspended() = 0;
+ // Reads only data which is cached in the media cache. If you try to read
+ // any data which overlaps uncached data, or if aCount bytes otherwise can't
+ // be read, this function will return failure. This function be called from
+ // any thread, and it is the only read operation which is safe to call on
+ // the main thread, since it's guaranteed to be non blocking.
+ virtual nsresult ReadFromCache(char* aBuffer,
+ int64_t aOffset,
+ uint32_t aCount) = 0;
+ // Returns true if the resource can be seeked to unbuffered ranges, i.e.
+ // for an HTTP network stream this returns true if HTTP1.1 Byte Range
+ // requests are supported by the connection/server.
+ virtual bool IsTransportSeekable() = 0;
+
+ /**
+ * Create a resource, reading data from the channel. Call on main thread only.
+ * The caller must follow up by calling resource->Open().
+ */
+ static already_AddRefed<MediaResource> Create(MediaResourceCallback* aCallback, nsIChannel* aChannel);
+
+ /**
+ * Open the stream. This creates a stream listener and returns it in
+ * aStreamListener; this listener needs to be notified of incoming data.
+ */
+ virtual nsresult Open(nsIStreamListener** aStreamListener) = 0;
+
+ /**
+ * Fills aRanges with MediaByteRanges representing the data which is cached
+ * in the media cache. Stream should be pinned during call and while
+ * aRanges is being used.
+ */
+ virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) = 0;
+
+ // Ensure that the media cache writes any data held in its partial block.
+ // Called on the main thread only.
+ virtual void FlushCache() { }
+
+ // Notify that the last data byte range was loaded.
+ virtual void NotifyLastByteRange() { }
+
+ // Returns the content type of the resource. This is copied from the
+ // nsIChannel when the MediaResource is created. Safe to call from
+ // any thread.
+ virtual const nsCString& GetContentType() const = 0;
+
+ // Return true if the stream is a live stream
+ virtual bool IsRealTime() {
+ return false;
+ }
+
+ // Returns true if the resource is a live stream.
+ virtual bool IsLiveStream()
+ {
+ return GetLength() == -1;
+ }
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return 0;
+ }
+
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ const nsCString& GetContentURL() const { return EmptyCString(); }
+
+protected:
+ virtual ~MediaResource() {};
+
+private:
+ void Destroy();
+};
+
+class BaseMediaResource : public MediaResource {
+public:
+ nsIURI* URI() const override { return mURI; }
+ void SetLoadInBackground(bool aLoadInBackground) override;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
+ {
+ // Might be useful to track in the future:
+ // - mChannel
+ // - mURI (possibly owned, looks like just a ref from mChannel)
+ // Not owned:
+ // - mCallback
+ size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
+ size += mContentType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ return size;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // Returns the url of the resource. Safe to call from any thread?
+ const nsCString& GetContentURL() const
+ {
+ return mContentURL;
+ }
+
+protected:
+ BaseMediaResource(MediaResourceCallback* aCallback,
+ nsIChannel* aChannel,
+ nsIURI* aURI,
+ const nsACString& aContentType) :
+ mCallback(aCallback),
+ mChannel(aChannel),
+ mURI(aURI),
+ mContentType(aContentType),
+ mLoadInBackground(false)
+ {
+ MOZ_COUNT_CTOR(BaseMediaResource);
+ NS_ASSERTION(!mContentType.IsEmpty(), "Must know content type");
+ mURI->GetSpec(mContentURL);
+ }
+ virtual ~BaseMediaResource()
+ {
+ MOZ_COUNT_DTOR(BaseMediaResource);
+ }
+
+ const nsCString& GetContentType() const override
+ {
+ return mContentType;
+ }
+
+ // Set the request's load flags to aFlags. If the request is part of a
+ // load group, the request is removed from the group, the flags are set, and
+ // then the request is added back to the load group.
+ void ModifyLoadFlags(nsLoadFlags aFlags);
+
+ // Dispatches an event to call MediaDecoder::NotifyBytesConsumed(aNumBytes, aOffset)
+ // on the main thread. This is called automatically after every read.
+ void DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset);
+
+ RefPtr<MediaResourceCallback> mCallback;
+
+ // Channel used to download the media data. Must be accessed
+ // from the main thread only.
+ nsCOMPtr<nsIChannel> mChannel;
+
+ // URI in case the stream needs to be re-opened. Access from
+ // main thread only.
+ nsCOMPtr<nsIURI> mURI;
+
+ // Content-Type of the channel. This is copied from the nsIChannel when the
+ // MediaResource is created. This is constant, so accessing from any thread
+ // is safe.
+ const nsCString mContentType;
+
+ // Copy of the url of the channel resource.
+ nsCString mContentURL;
+
+ // True if SetLoadInBackground() has been called with
+ // aLoadInBackground = true, i.e. when the document load event is not
+ // blocked by this resource, and all channel loads will be in the
+ // background.
+ bool mLoadInBackground;
+};
+
+
+/**
+ * This class is responsible for managing the suspend count and report suspend
+ * status of channel.
+ **/
+class ChannelSuspendAgent {
+public:
+ explicit ChannelSuspendAgent(nsIChannel* aChannel)
+ : mChannel(aChannel),
+ mSuspendCount(0),
+ mIsChannelSuspended(false)
+ {}
+
+ // True when the channel has been suspended or needs to be suspended.
+ bool IsSuspended();
+
+ // Return true when the channel is logically suspended, i.e. the suspend
+ // count goes from 0 to 1.
+ bool Suspend();
+
+ // Return true only when the suspend count is equal to zero.
+ bool Resume();
+
+ // Call after opening channel, set channel and check whether the channel
+ // needs to be suspended.
+ void NotifyChannelOpened(nsIChannel* aChannel);
+
+ // Call before closing channel, reset the channel internal status if needed.
+ void NotifyChannelClosing();
+
+ // Check whether we need to suspend the channel.
+ void UpdateSuspendedStatusIfNeeded();
+private:
+ // Only suspends channel but not changes the suspend count.
+ void SuspendInternal();
+
+ nsIChannel* mChannel;
+ Atomic<uint32_t> mSuspendCount;
+ bool mIsChannelSuspended;
+};
+
+/**
+ * This is the MediaResource implementation that wraps Necko channels.
+ * Much of its functionality is actually delegated to MediaCache via
+ * an underlying MediaCacheStream.
+ *
+ * All synchronization is performed by MediaCacheStream; all off-main-
+ * thread operations are delegated directly to that object.
+ */
+class ChannelMediaResource : public BaseMediaResource
+{
+public:
+ ChannelMediaResource(MediaResourceCallback* aDecoder,
+ nsIChannel* aChannel,
+ nsIURI* aURI,
+ const nsACString& aContentType);
+ ~ChannelMediaResource();
+
+ // These are called on the main thread by MediaCache. These must
+ // not block or grab locks, because the media cache is holding its lock.
+ // Notify that data is available from the cache. This can happen even
+ // if this stream didn't read any data, since another stream might have
+ // received data for the same resource.
+ void CacheClientNotifyDataReceived();
+ // Notify that we reached the end of the stream. This can happen even
+ // if this stream didn't read any data, since another stream might have
+ // received data for the same resource.
+ void CacheClientNotifyDataEnded(nsresult aStatus);
+ // Notify that the principal for the cached resource changed.
+ void CacheClientNotifyPrincipalChanged();
+ // Notify the decoder that the cache suspended status changed.
+ void CacheClientNotifySuspendedStatusChanged();
+
+ // These are called on the main thread by MediaCache. These shouldn't block,
+ // but they may grab locks --- the media cache is not holding its lock
+ // when these are called.
+ // Start a new load at the given aOffset. The old load is cancelled
+ // and no more data from the old load will be notified via
+ // MediaCacheStream::NotifyDataReceived/Ended.
+ // This can fail.
+ nsresult CacheClientSeek(int64_t aOffset, bool aResume);
+ // Suspend the current load since data is currently not wanted
+ nsresult CacheClientSuspend();
+ // Resume the current load since data is wanted again
+ nsresult CacheClientResume();
+
+ // Ensure that the media cache writes any data held in its partial block.
+ // Called on the main thread.
+ void FlushCache() override;
+
+ // Notify that the last data byte range was loaded.
+ void NotifyLastByteRange() override;
+
+ // Main thread
+ nsresult Open(nsIStreamListener** aStreamListener) override;
+ nsresult Close() override;
+ void Suspend(bool aCloseImmediately) override;
+ void Resume() override;
+ already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
+ // Return true if the stream has been closed.
+ bool IsClosed() const { return mCacheStream.IsClosed(); }
+ bool CanClone() override;
+ already_AddRefed<MediaResource> CloneData(MediaResourceCallback* aDecoder) override;
+ // Set statistics to be recorded to the object passed in. If not called,
+ // |ChannelMediaResource| will create it's own statistics objects in |Open|.
+ void RecordStatisticsTo(MediaChannelStatistics *aStatistics) override {
+ NS_ASSERTION(aStatistics, "Statistics param cannot be null!");
+ MutexAutoLock lock(mLock);
+ if (!mChannelStatistics) {
+ mChannelStatistics = aStatistics;
+ }
+ }
+ nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override;
+ void EnsureCacheUpToDate() override;
+
+ // Other thread
+ void SetReadMode(MediaCacheStream::ReadMode aMode) override;
+ void SetPlaybackRate(uint32_t aBytesPerSecond) override;
+ nsresult ReadAt(int64_t offset, char* aBuffer,
+ uint32_t aCount, uint32_t* aBytes) override;
+ already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) override;
+ int64_t Tell() override;
+
+ // Any thread
+ void Pin() override;
+ void Unpin() override;
+ double GetDownloadRate(bool* aIsReliable) override;
+ int64_t GetLength() override;
+ int64_t GetNextCachedData(int64_t aOffset) override;
+ int64_t GetCachedDataEnd(int64_t aOffset) override;
+ bool IsDataCachedToEndOfResource(int64_t aOffset) override;
+ bool IsSuspendedByCache() override;
+ bool IsSuspended() override;
+ bool IsTransportSeekable() override;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Might be useful to track in the future:
+ // - mListener (seems minor)
+ // - mChannelStatistics (seems minor)
+ // owned if RecordStatisticsTo is not called
+ // - mDataReceivedEvent (seems minor)
+ size_t size = BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
+ size += mCacheStream.SizeOfExcludingThis(aMallocSizeOf);
+
+ return size;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ class Listener final : public nsIStreamListener,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink
+ {
+ ~Listener() {}
+ public:
+ explicit Listener(ChannelMediaResource* aResource) : mResource(aResource) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ void Revoke() { mResource = nullptr; }
+
+ private:
+ RefPtr<ChannelMediaResource> mResource;
+ };
+ friend class Listener;
+
+ nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override;
+
+protected:
+ // These are called on the main thread by Listener.
+ nsresult OnStartRequest(nsIRequest* aRequest);
+ nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);
+ nsresult OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint32_t aCount);
+ nsresult OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags);
+
+ // Opens the channel, using an HTTP byte range request to start at mOffset
+ // if possible. Main thread only.
+ nsresult OpenChannel(nsIStreamListener** aStreamListener);
+ nsresult RecreateChannel();
+ // Add headers to HTTP request. Main thread only.
+ nsresult SetupChannelHeaders();
+ // Closes the channel. Main thread only.
+ void CloseChannel();
+
+ // Parses 'Content-Range' header and returns results via parameters.
+ // Returns error if header is not available, values are not parse-able or
+ // values are out of range.
+ nsresult ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
+ int64_t& aRangeStart,
+ int64_t& aRangeEnd,
+ int64_t& aRangeTotal);
+
+ void DoNotifyDataReceived();
+
+ static nsresult CopySegmentToCache(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount);
+
+ // Main thread access only
+ int64_t mOffset;
+ RefPtr<Listener> mListener;
+ // A data received event for the decoder that has been dispatched but has
+ // not yet been processed.
+ nsRevocableEventPtr<nsRunnableMethod<ChannelMediaResource, void, false> > mDataReceivedEvent;
+ // When this flag is set, if we get a network error we should silently
+ // reopen the stream.
+ bool mReopenOnError;
+ // When this flag is set, we should not report the next close of the
+ // channel.
+ bool mIgnoreClose;
+
+ // Any thread access
+ MediaCacheStream mCacheStream;
+
+ // This lock protects mChannelStatistics
+ Mutex mLock;
+ RefPtr<MediaChannelStatistics> mChannelStatistics;
+
+ // True if we couldn't suspend the stream and we therefore don't want
+ // to resume later. This is usually due to the channel not being in the
+ // isPending state at the time of the suspend request.
+ bool mIgnoreResume;
+
+ ChannelSuspendAgent mSuspendAgent;
+};
+
+/**
+ * RAII class that handles pinning and unpinning for MediaResource and derived.
+ * This should be used when making calculations that involve potentially-cached
+ * MediaResource data, so that the state of the world can't change out from under
+ * us.
+ */
+template<class T>
+class MOZ_RAII AutoPinned {
+ public:
+ explicit AutoPinned(T* aResource MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : mResource(aResource) {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ MOZ_ASSERT(mResource);
+ mResource->Pin();
+ }
+
+ ~AutoPinned() {
+ mResource->Unpin();
+ }
+
+ operator T*() const { return mResource; }
+ T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mResource; }
+
+private:
+ T* mResource;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+/*
+ * MediaResourceIndex provides a way to access MediaResource objects.
+ * Read, Seek and Tell must only be called on non-main threads.
+ * In the case of the Ogg Decoder they are called on the Decode thread for
+ * example. You must ensure that no threads are calling these methods once
+ * the MediaResource has been Closed.
+ */
+
+class MediaResourceIndex
+{
+public:
+ explicit MediaResourceIndex(MediaResource* aResource)
+ : mResource(aResource)
+ , mOffset(0)
+ {}
+
+ // Read up to aCount bytes from the stream. The buffer must have
+ // enough room for at least aCount bytes. Stores the number of
+ // actual bytes read in aBytes (0 on end of file).
+ // May read less than aCount bytes if the number of
+ // available bytes is less than aCount. Always check *aBytes after
+ // read, and call again if necessary.
+ nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
+ // Seek to the given bytes offset in the stream. aWhence can be
+ // one of:
+ // NS_SEEK_SET
+ // NS_SEEK_CUR
+ // NS_SEEK_END
+ //
+ // In the Http strategy case the cancel will cause the http
+ // channel's listener to close the pipe, forcing an i/o error on any
+ // blocked read. This will allow the decode thread to complete the
+ // event.
+ //
+ // In the case of a seek in progress, the byte range request creates
+ // a new listener. This is done on the main thread via seek
+ // synchronously dispatching an event. This avoids the issue of us
+ // closing the listener but an outstanding byte range request
+ // creating a new one. They run on the same thread so no explicit
+ // synchronisation is required. The byte range request checks for
+ // the cancel flag and does not create a new channel or listener if
+ // we are cancelling.
+ //
+ // The default strategy does not do any seeking - the only issue is
+ // a blocked read which it handles by causing the listener to close
+ // the pipe, as per the http case.
+ //
+ // The file strategy doesn't block for any great length of time so
+ // is fine for a no-op cancel.
+ nsresult Seek(int32_t aWhence, int64_t aOffset);
+ // Report the current offset in bytes from the start of the stream.
+ int64_t Tell() const { return mOffset; }
+
+ // Return the underlying MediaResource.
+ MediaResource* GetResource() const { return mResource; }
+
+ // Read up to aCount bytes from the stream. The read starts at
+ // aOffset in the stream, seeking to that location initially if
+ // it is not the current stream offset.
+ // Unlike MediaResource::ReadAt, ReadAt only returns fewer bytes than
+ // requested if end of stream or an error is encountered. There is no need to
+ // call it again to get more data.
+ // *aBytes will contain the number of bytes copied, even if an error occurred.
+ // ReadAt doesn't have an impact on the offset returned by Tell().
+ nsresult ReadAt(int64_t aOffset, char* aBuffer,
+ uint32_t aCount, uint32_t* aBytes) const;
+
+ // Convenience methods, directly calling the MediaResource method of the same
+ // name.
+ // Those functions do not update the MediaResource offset as returned
+ // by Tell().
+
+ // This method returns nullptr if anything fails.
+ // Otherwise, it returns an owned buffer.
+ // MediaReadAt may return fewer bytes than requested if end of stream is
+ // encountered. There is no need to call it again to get more data.
+ already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) const
+ {
+ return mResource->MediaReadAt(aOffset, aCount);
+ }
+ // Get the length of the stream in bytes. Returns -1 if not known.
+ // This can change over time; after a seek operation, a misbehaving
+ // server may give us a resource of a different length to what it had
+ // reported previously --- or it may just lie in its Content-Length
+ // header and give us more or less data than it reported. We will adjust
+ // the result of GetLength to reflect the data that's actually arriving.
+ int64_t GetLength() const { return mResource->GetLength(); }
+
+private:
+ RefPtr<MediaResource> mResource;
+ int64_t mOffset;
+};
+
+} // namespace mozilla
+
+#endif