michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #if !defined(MediaResource_h_) michael@0: #define MediaResource_h_ michael@0: michael@0: #include "mozilla/Mutex.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIStreamingProtocolController.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsIChannelEventSink.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "MediaCache.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: // For HTTP seeking, if number of bytes needing to be michael@0: // seeked forward is less than this value then a read is michael@0: // done rather than a byte range request. michael@0: static const int64_t SEEK_VS_READ_THRESHOLD = 32*1024; michael@0: michael@0: static const uint32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE = 416; michael@0: michael@0: // Number of bytes we have accumulated before we assume the connection download michael@0: // rate can be reliably calculated. 57 Segments at IW=3 allows slow start to michael@0: // reach a CWND of 30 (See bug 831998) michael@0: static const int64_t RELIABLE_DATA_THRESHOLD = 57 * 1460; michael@0: michael@0: class nsIHttpChannel; michael@0: class nsIPrincipal; michael@0: michael@0: namespace mozilla { michael@0: michael@0: class MediaDecoder; michael@0: michael@0: /** michael@0: * This class is useful for estimating rates of data passing through michael@0: * some channel. The idea is that activity on the channel "starts" michael@0: * and "stops" over time. At certain times data passes through the michael@0: * channel (usually while the channel is active; data passing through michael@0: * an inactive channel is ignored). The GetRate() function computes michael@0: * an estimate of the "current rate" of the channel, which is some michael@0: * kind of average of the data passing through over the time the michael@0: * channel is active. michael@0: * michael@0: * All methods take "now" as a parameter so the user of this class can michael@0: * control the timeline used. michael@0: */ michael@0: class MediaChannelStatistics { michael@0: public: michael@0: MediaChannelStatistics() { Reset(); } michael@0: michael@0: MediaChannelStatistics(MediaChannelStatistics * aCopyFrom) michael@0: { michael@0: MOZ_ASSERT(aCopyFrom); michael@0: mAccumulatedBytes = aCopyFrom->mAccumulatedBytes; michael@0: mAccumulatedTime = aCopyFrom->mAccumulatedTime; michael@0: mLastStartTime = aCopyFrom->mLastStartTime; michael@0: mIsStarted = aCopyFrom->mIsStarted; michael@0: } michael@0: michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaChannelStatistics) michael@0: michael@0: void Reset() { michael@0: mLastStartTime = TimeStamp(); michael@0: mAccumulatedTime = TimeDuration(0); michael@0: mAccumulatedBytes = 0; michael@0: mIsStarted = false; michael@0: } michael@0: void Start() { michael@0: if (mIsStarted) michael@0: return; michael@0: mLastStartTime = TimeStamp::Now(); michael@0: mIsStarted = true; michael@0: } michael@0: void Stop() { michael@0: if (!mIsStarted) michael@0: return; michael@0: mAccumulatedTime += TimeStamp::Now() - mLastStartTime; michael@0: mIsStarted = false; michael@0: } michael@0: void AddBytes(int64_t aBytes) { michael@0: if (!mIsStarted) { michael@0: // ignore this data, it may be related to seeking or some other michael@0: // operation we don't care about michael@0: return; michael@0: } michael@0: mAccumulatedBytes += aBytes; michael@0: } michael@0: double GetRateAtLastStop(bool* aReliable) { michael@0: double seconds = mAccumulatedTime.ToSeconds(); michael@0: *aReliable = (seconds >= 1.0) || michael@0: (mAccumulatedBytes >= RELIABLE_DATA_THRESHOLD); michael@0: if (seconds <= 0.0) michael@0: return 0.0; michael@0: return static_cast(mAccumulatedBytes)/seconds; michael@0: } michael@0: double GetRate(bool* aReliable) { michael@0: TimeDuration time = mAccumulatedTime; michael@0: if (mIsStarted) { michael@0: time += TimeStamp::Now() - mLastStartTime; michael@0: } michael@0: double seconds = time.ToSeconds(); michael@0: *aReliable = (seconds >= 3.0) || michael@0: (mAccumulatedBytes >= RELIABLE_DATA_THRESHOLD); michael@0: if (seconds <= 0.0) michael@0: return 0.0; michael@0: return static_cast(mAccumulatedBytes)/seconds; michael@0: } michael@0: private: michael@0: int64_t mAccumulatedBytes; michael@0: TimeDuration mAccumulatedTime; michael@0: TimeStamp mLastStartTime; michael@0: bool mIsStarted; michael@0: }; michael@0: michael@0: // Forward declaration for use in MediaByteRange. michael@0: class TimestampedMediaByteRange; michael@0: michael@0: // Represents a section of contiguous media, with a start and end offset. michael@0: // Used to denote ranges of data which are cached. michael@0: class MediaByteRange { michael@0: public: michael@0: MediaByteRange() : mStart(0), mEnd(0) {} michael@0: michael@0: MediaByteRange(int64_t aStart, int64_t aEnd) michael@0: : mStart(aStart), mEnd(aEnd) michael@0: { michael@0: NS_ASSERTION(mStart < mEnd, "Range should end after start!"); michael@0: } michael@0: michael@0: MediaByteRange(TimestampedMediaByteRange& aByteRange); michael@0: michael@0: bool IsNull() const { michael@0: return mStart == 0 && mEnd == 0; michael@0: } michael@0: michael@0: // Clears byte range values. michael@0: void Clear() { michael@0: mStart = 0; michael@0: mEnd = 0; michael@0: } michael@0: michael@0: int64_t mStart, mEnd; michael@0: }; michael@0: michael@0: // Represents a section of contiguous media, with a start and end offset, and michael@0: // a timestamp representing the start time. michael@0: class TimestampedMediaByteRange : public MediaByteRange { michael@0: public: michael@0: TimestampedMediaByteRange() : MediaByteRange(), mStartTime(-1) {} michael@0: michael@0: TimestampedMediaByteRange(int64_t aStart, int64_t aEnd, int64_t aStartTime) michael@0: : MediaByteRange(aStart, aEnd), mStartTime(aStartTime) michael@0: { michael@0: NS_ASSERTION(aStartTime >= 0, "Start time should not be negative!"); michael@0: } michael@0: michael@0: bool IsNull() const { michael@0: return MediaByteRange::IsNull() && mStartTime == -1; michael@0: } michael@0: michael@0: // Clears byte range values. michael@0: void Clear() { michael@0: MediaByteRange::Clear(); michael@0: mStartTime = -1; michael@0: } michael@0: michael@0: // In usecs. michael@0: int64_t mStartTime; michael@0: }; michael@0: michael@0: inline MediaByteRange::MediaByteRange(TimestampedMediaByteRange& aByteRange) michael@0: : mStart(aByteRange.mStart), mEnd(aByteRange.mEnd) michael@0: { michael@0: NS_ASSERTION(mStart < mEnd, "Range should end after start!"); michael@0: } michael@0: michael@0: class RtspMediaResource; michael@0: michael@0: /** michael@0: * Provides a thread-safe, seek/read interface to resources michael@0: * loaded from a URI. Uses MediaCache to cache data received over michael@0: * Necko's async channel API, thus resolving the mismatch between clients michael@0: * that need efficient random access to the data and protocols that do not michael@0: * support efficient random access, such as HTTP. michael@0: * michael@0: * Instances of this class must be created on the main thread. michael@0: * Most methods must be called on the main thread only. Read, Seek and michael@0: * Tell must only be called on non-main threads. In the case of the Ogg michael@0: * Decoder they are called on the Decode thread for example. You must michael@0: * ensure that no threads are calling these methods once Close is called. michael@0: * michael@0: * Instances of this class are reference counted. Use nsRefPtr for michael@0: * managing the lifetime of instances of this class. michael@0: * michael@0: * The generic implementation of this class is ChannelMediaResource, which can michael@0: * handle any URI for which Necko supports AsyncOpen. michael@0: * The 'file:' protocol can be implemented efficiently with direct random michael@0: * access, so the FileMediaResource implementation class bypasses the cache. michael@0: * MediaResource::Create automatically chooses the best implementation class. michael@0: */ michael@0: class MediaResource : public nsISupports michael@0: { michael@0: public: michael@0: // Our refcounting is threadsafe, and when our refcount drops to zero michael@0: // we dispatch an event to the main thread to delete the MediaResource. michael@0: // Note that this means it's safe for references to this object to be michael@0: // released on a non main thread, but the destructor will always run on michael@0: // the main thread. michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: // The following can be called on the main thread only: michael@0: // Get the URI michael@0: virtual nsIURI* URI() const { return nullptr; } michael@0: // Close the resource, stop any listeners, channels, etc. michael@0: // Cancels any currently blocking Read request and forces that request to michael@0: // return an error. michael@0: virtual nsresult Close() = 0; michael@0: // Suspend any downloads that are in progress. michael@0: // If aCloseImmediately is set, resources should be released immediately michael@0: // since we don't expect to resume again any time soon. Otherwise we michael@0: // may resume again soon so resources should be held for a little michael@0: // while. michael@0: virtual void Suspend(bool aCloseImmediately) = 0; michael@0: // Resume any downloads that have been suspended. michael@0: virtual void Resume() = 0; michael@0: // Get the current principal for the channel michael@0: virtual already_AddRefed GetCurrentPrincipal() = 0; michael@0: // If this returns false, then we shouldn't try to clone this MediaResource michael@0: // because its underlying resources are not suitable for reuse (e.g. michael@0: // because the underlying connection has been lost, or this resource michael@0: // just can't be safely cloned). If this returns true, CloneData could michael@0: // still fail. If this returns false, CloneData should not be called. michael@0: virtual bool CanClone() { return false; } michael@0: // Create a new stream of the same type that refers to the same URI michael@0: // with a new channel. Any cached data associated with the original michael@0: // stream should be accessible in the new stream too. michael@0: virtual already_AddRefed CloneData(MediaDecoder* aDecoder) = 0; michael@0: // Set statistics to be recorded to the object passed in. michael@0: virtual void RecordStatisticsTo(MediaChannelStatistics *aStatistics) { } michael@0: michael@0: // These methods are called off the main thread. michael@0: // The mode is initially MODE_PLAYBACK. michael@0: virtual void SetReadMode(MediaCacheStream::ReadMode aMode) = 0; michael@0: // This is the client's estimate of the playback rate assuming michael@0: // the media plays continuously. The cache can't guess this itself michael@0: // because it doesn't know when the decoder was paused, buffering, etc. michael@0: virtual void SetPlaybackRate(uint32_t aBytesPerSecond) = 0; michael@0: // Read up to aCount bytes from the stream. The buffer must have michael@0: // enough room for at least aCount bytes. Stores the number of michael@0: // actual bytes read in aBytes (0 on end of file). michael@0: // May read less than aCount bytes if the number of michael@0: // available bytes is less than aCount. Always check *aBytes after michael@0: // read, and call again if necessary. michael@0: virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) = 0; michael@0: // Read up to aCount bytes from the stream. The read starts at michael@0: // aOffset in the stream, seeking to that location initially if michael@0: // it is not the current stream offset. The remaining arguments, michael@0: // results and requirements are the same as per the Read method. michael@0: virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, michael@0: uint32_t aCount, uint32_t* aBytes) = 0; michael@0: // Seek to the given bytes offset in the stream. aWhence can be michael@0: // one of: michael@0: // NS_SEEK_SET michael@0: // NS_SEEK_CUR michael@0: // NS_SEEK_END michael@0: // michael@0: // In the Http strategy case the cancel will cause the http michael@0: // channel's listener to close the pipe, forcing an i/o error on any michael@0: // blocked read. This will allow the decode thread to complete the michael@0: // event. michael@0: // michael@0: // In the case of a seek in progress, the byte range request creates michael@0: // a new listener. This is done on the main thread via seek michael@0: // synchronously dispatching an event. This avoids the issue of us michael@0: // closing the listener but an outstanding byte range request michael@0: // creating a new one. They run on the same thread so no explicit michael@0: // synchronisation is required. The byte range request checks for michael@0: // the cancel flag and does not create a new channel or listener if michael@0: // we are cancelling. michael@0: // michael@0: // The default strategy does not do any seeking - the only issue is michael@0: // a blocked read which it handles by causing the listener to close michael@0: // the pipe, as per the http case. michael@0: // michael@0: // The file strategy doesn't block for any great length of time so michael@0: // is fine for a no-op cancel. michael@0: virtual nsresult Seek(int32_t aWhence, int64_t aOffset) = 0; michael@0: virtual void StartSeekingForMetadata() = 0; michael@0: virtual void EndSeekingForMetadata() = 0; michael@0: // Report the current offset in bytes from the start of the stream. michael@0: virtual int64_t Tell() = 0; michael@0: // Moves any existing channel loads into the background, so that they don't michael@0: // block the load event. Any new loads initiated (for example to seek) michael@0: // will also be in the background. michael@0: virtual void MoveLoadsToBackground() {} michael@0: // Ensures that the value returned by IsSuspendedByCache below is up to date michael@0: // (i.e. the cache has examined this stream at least once). michael@0: virtual void EnsureCacheUpToDate() {} michael@0: michael@0: // These can be called on any thread. michael@0: // Cached blocks associated with this stream will not be evicted michael@0: // while the stream is pinned. michael@0: virtual void Pin() = 0; michael@0: virtual void Unpin() = 0; michael@0: // Get the estimated download rate in bytes per second (assuming no michael@0: // pausing of the channel is requested by Gecko). michael@0: // *aIsReliable is set to true if we think the estimate is useful. michael@0: virtual double GetDownloadRate(bool* aIsReliable) = 0; michael@0: // Get the length of the stream in bytes. Returns -1 if not known. michael@0: // This can change over time; after a seek operation, a misbehaving michael@0: // server may give us a resource of a different length to what it had michael@0: // reported previously --- or it may just lie in its Content-Length michael@0: // header and give us more or less data than it reported. We will adjust michael@0: // the result of GetLength to reflect the data that's actually arriving. michael@0: virtual int64_t GetLength() = 0; michael@0: // Returns the offset of the first byte of cached data at or after aOffset, michael@0: // or -1 if there is no such cached data. michael@0: virtual int64_t GetNextCachedData(int64_t aOffset) = 0; michael@0: // Returns the end of the bytes starting at the given offset michael@0: // which are in cache. michael@0: virtual int64_t GetCachedDataEnd(int64_t aOffset) = 0; michael@0: // Returns true if all the data from aOffset to the end of the stream michael@0: // is in cache. If the end of the stream is not known, we return false. michael@0: virtual bool IsDataCachedToEndOfResource(int64_t aOffset) = 0; michael@0: // Returns true if this stream is suspended by the cache because the michael@0: // cache is full. If true then the decoder should try to start consuming michael@0: // data, otherwise we may not be able to make progress. michael@0: // MediaDecoder::NotifySuspendedStatusChanged is called when this michael@0: // changes. michael@0: // For resources using the media cache, this returns true only when all michael@0: // streams for the same resource are all suspended. michael@0: virtual bool IsSuspendedByCache() = 0; michael@0: // Returns true if this stream has been suspended. michael@0: virtual bool IsSuspended() = 0; michael@0: // Reads only data which is cached in the media cache. If you try to read michael@0: // any data which overlaps uncached data, or if aCount bytes otherwise can't michael@0: // be read, this function will return failure. This function be called from michael@0: // any thread, and it is the only read operation which is safe to call on michael@0: // the main thread, since it's guaranteed to be non blocking. michael@0: virtual nsresult ReadFromCache(char* aBuffer, michael@0: int64_t aOffset, michael@0: uint32_t aCount) = 0; michael@0: // Returns true if the resource can be seeked to unbuffered ranges, i.e. michael@0: // for an HTTP network stream this returns true if HTTP1.1 Byte Range michael@0: // requests are supported by the connection/server. michael@0: virtual bool IsTransportSeekable() = 0; michael@0: michael@0: /** michael@0: * Create a resource, reading data from the channel. Call on main thread only. michael@0: * The caller must follow up by calling resource->Open(). michael@0: */ michael@0: static already_AddRefed Create(MediaDecoder* aDecoder, nsIChannel* aChannel); michael@0: michael@0: /** michael@0: * Open the stream. This creates a stream listener and returns it in michael@0: * aStreamListener; this listener needs to be notified of incoming data. michael@0: */ michael@0: virtual nsresult Open(nsIStreamListener** aStreamListener) = 0; michael@0: michael@0: /** michael@0: * Fills aRanges with MediaByteRanges representing the data which is cached michael@0: * in the media cache. Stream should be pinned during call and while michael@0: * aRanges is being used. michael@0: */ michael@0: virtual nsresult GetCachedRanges(nsTArray& aRanges) = 0; michael@0: michael@0: // Ensure that the media cache writes any data held in its partial block. michael@0: // Called on the main thread only. michael@0: virtual void FlushCache() { } michael@0: michael@0: // Notify that the last data byte range was loaded. michael@0: virtual void NotifyLastByteRange() { } michael@0: michael@0: // Returns the content type of the resource. This is copied from the michael@0: // nsIChannel when the MediaResource is created. Safe to call from michael@0: // any thread. michael@0: virtual const nsCString& GetContentType() const = 0; michael@0: michael@0: // Get the RtspMediaResource pointer if this MediaResource really is a michael@0: // RtspMediaResource. For calling Rtsp specific functions. michael@0: virtual RtspMediaResource* GetRtspPointer() { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Return true if the stream is a live stream michael@0: virtual bool IsRealTime() { michael@0: return false; michael@0: } michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { michael@0: return 0; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: protected: michael@0: virtual ~MediaResource() {}; michael@0: michael@0: private: michael@0: void Destroy(); michael@0: }; michael@0: michael@0: class BaseMediaResource : public MediaResource { michael@0: public: michael@0: virtual nsIURI* URI() const { return mURI; } michael@0: virtual void MoveLoadsToBackground(); michael@0: michael@0: virtual size_t SizeOfExcludingThis( michael@0: MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: // Might be useful to track in the future: michael@0: // - mChannel michael@0: // - mURI (possibly owned, looks like just a ref from mChannel) michael@0: // Not owned: michael@0: // - mDecoder michael@0: size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf); michael@0: size += mContentType.SizeOfIncludingThisIfUnshared(aMallocSizeOf); michael@0: michael@0: return size; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis( michael@0: MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: protected: michael@0: BaseMediaResource(MediaDecoder* aDecoder, michael@0: nsIChannel* aChannel, michael@0: nsIURI* aURI, michael@0: const nsACString& aContentType) : michael@0: mDecoder(aDecoder), michael@0: mChannel(aChannel), michael@0: mURI(aURI), michael@0: mContentType(aContentType), michael@0: mLoadInBackground(false) michael@0: { michael@0: MOZ_COUNT_CTOR(BaseMediaResource); michael@0: NS_ASSERTION(!mContentType.IsEmpty(), "Must know content type"); michael@0: } michael@0: virtual ~BaseMediaResource() michael@0: { michael@0: MOZ_COUNT_DTOR(BaseMediaResource); michael@0: } michael@0: michael@0: virtual const nsCString& GetContentType() const MOZ_OVERRIDE michael@0: { michael@0: return mContentType; michael@0: } michael@0: michael@0: // Set the request's load flags to aFlags. If the request is part of a michael@0: // load group, the request is removed from the group, the flags are set, and michael@0: // then the request is added back to the load group. michael@0: void ModifyLoadFlags(nsLoadFlags aFlags); michael@0: michael@0: // Dispatches an event to call MediaDecoder::NotifyBytesConsumed(aNumBytes, aOffset) michael@0: // on the main thread. This is called automatically after every read. michael@0: void DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset); michael@0: michael@0: // This is not an nsCOMPointer to prevent a circular reference michael@0: // between the decoder to the media stream object. The stream never michael@0: // outlives the lifetime of the decoder. michael@0: MediaDecoder* mDecoder; michael@0: michael@0: // Channel used to download the media data. Must be accessed michael@0: // from the main thread only. michael@0: nsCOMPtr mChannel; michael@0: michael@0: // URI in case the stream needs to be re-opened. Access from michael@0: // main thread only. michael@0: nsCOMPtr mURI; michael@0: michael@0: // Content-Type of the channel. This is copied from the nsIChannel when the michael@0: // MediaResource is created. This is constant, so accessing from any thread michael@0: // is safe. michael@0: const nsAutoCString mContentType; michael@0: michael@0: // True if MoveLoadsToBackground() has been called, i.e. the load event michael@0: // has been fired, and all channel loads will be in the background. michael@0: bool mLoadInBackground; michael@0: }; michael@0: michael@0: /** michael@0: * This is the MediaResource implementation that wraps Necko channels. michael@0: * Much of its functionality is actually delegated to MediaCache via michael@0: * an underlying MediaCacheStream. michael@0: * michael@0: * All synchronization is performed by MediaCacheStream; all off-main- michael@0: * thread operations are delegated directly to that object. michael@0: */ michael@0: class ChannelMediaResource : public BaseMediaResource michael@0: { michael@0: public: michael@0: ChannelMediaResource(MediaDecoder* aDecoder, michael@0: nsIChannel* aChannel, michael@0: nsIURI* aURI, michael@0: const nsACString& aContentType); michael@0: ~ChannelMediaResource(); michael@0: michael@0: // These are called on the main thread by MediaCache. These must michael@0: // not block or grab locks, because the media cache is holding its lock. michael@0: // Notify that data is available from the cache. This can happen even michael@0: // if this stream didn't read any data, since another stream might have michael@0: // received data for the same resource. michael@0: void CacheClientNotifyDataReceived(); michael@0: // Notify that we reached the end of the stream. This can happen even michael@0: // if this stream didn't read any data, since another stream might have michael@0: // received data for the same resource. michael@0: void CacheClientNotifyDataEnded(nsresult aStatus); michael@0: // Notify that the principal for the cached resource changed. michael@0: void CacheClientNotifyPrincipalChanged(); michael@0: michael@0: // These are called on the main thread by MediaCache. These shouldn't block, michael@0: // but they may grab locks --- the media cache is not holding its lock michael@0: // when these are called. michael@0: // Start a new load at the given aOffset. The old load is cancelled michael@0: // and no more data from the old load will be notified via michael@0: // MediaCacheStream::NotifyDataReceived/Ended. michael@0: // This can fail. michael@0: nsresult CacheClientSeek(int64_t aOffset, bool aResume); michael@0: // Suspend the current load since data is currently not wanted michael@0: nsresult CacheClientSuspend(); michael@0: // Resume the current load since data is wanted again michael@0: nsresult CacheClientResume(); michael@0: michael@0: // Ensure that the media cache writes any data held in its partial block. michael@0: // Called on the main thread. michael@0: virtual void FlushCache() MOZ_OVERRIDE; michael@0: michael@0: // Notify that the last data byte range was loaded. michael@0: virtual void NotifyLastByteRange() MOZ_OVERRIDE; michael@0: michael@0: // Main thread michael@0: virtual nsresult Open(nsIStreamListener** aStreamListener); michael@0: virtual nsresult Close(); michael@0: virtual void Suspend(bool aCloseImmediately); michael@0: virtual void Resume(); michael@0: virtual already_AddRefed GetCurrentPrincipal(); michael@0: // Return true if the stream has been closed. michael@0: bool IsClosed() const { return mCacheStream.IsClosed(); } michael@0: virtual bool CanClone(); michael@0: virtual already_AddRefed CloneData(MediaDecoder* aDecoder); michael@0: // Set statistics to be recorded to the object passed in. If not called, michael@0: // |ChannelMediaResource| will create it's own statistics objects in |Open|. michael@0: void RecordStatisticsTo(MediaChannelStatistics *aStatistics) MOZ_OVERRIDE { michael@0: NS_ASSERTION(aStatistics, "Statistics param cannot be null!"); michael@0: MutexAutoLock lock(mLock); michael@0: if (!mChannelStatistics) { michael@0: mChannelStatistics = aStatistics; michael@0: } michael@0: } michael@0: virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount); michael@0: virtual void EnsureCacheUpToDate(); michael@0: michael@0: // Other thread michael@0: virtual void SetReadMode(MediaCacheStream::ReadMode aMode); michael@0: virtual void SetPlaybackRate(uint32_t aBytesPerSecond); michael@0: virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes); michael@0: virtual nsresult ReadAt(int64_t offset, char* aBuffer, michael@0: uint32_t aCount, uint32_t* aBytes); michael@0: virtual nsresult Seek(int32_t aWhence, int64_t aOffset); michael@0: virtual void StartSeekingForMetadata(); michael@0: virtual void EndSeekingForMetadata(); michael@0: virtual int64_t Tell(); michael@0: michael@0: // Any thread michael@0: virtual void Pin(); michael@0: virtual void Unpin(); michael@0: virtual double GetDownloadRate(bool* aIsReliable); michael@0: virtual int64_t GetLength(); michael@0: virtual int64_t GetNextCachedData(int64_t aOffset); michael@0: virtual int64_t GetCachedDataEnd(int64_t aOffset); michael@0: virtual bool IsDataCachedToEndOfResource(int64_t aOffset); michael@0: virtual bool IsSuspendedByCache(); michael@0: virtual bool IsSuspended(); michael@0: virtual bool IsTransportSeekable() MOZ_OVERRIDE; michael@0: michael@0: virtual size_t SizeOfExcludingThis( michael@0: MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE { michael@0: // Might be useful to track in the future: michael@0: // - mListener (seems minor) michael@0: // - mChannelStatistics (seems minor) michael@0: // owned if RecordStatisticsTo is not called michael@0: // - mDataReceivedEvent (seems minor) michael@0: size_t size = BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf); michael@0: size += mCacheStream.SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: return size; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis( michael@0: MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: class Listener MOZ_FINAL : public nsIStreamListener, michael@0: public nsIInterfaceRequestor, michael@0: public nsIChannelEventSink michael@0: { michael@0: public: michael@0: Listener(ChannelMediaResource* aResource) : mResource(aResource) {} michael@0: ~Listener() {} michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSISTREAMLISTENER michael@0: NS_DECL_NSICHANNELEVENTSINK michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: michael@0: void Revoke() { mResource = nullptr; } michael@0: michael@0: private: michael@0: nsRefPtr mResource; michael@0: }; michael@0: friend class Listener; michael@0: michael@0: nsresult GetCachedRanges(nsTArray& aRanges); michael@0: michael@0: protected: michael@0: // These are called on the main thread by Listener. michael@0: nsresult OnStartRequest(nsIRequest* aRequest); michael@0: nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus); michael@0: nsresult OnDataAvailable(nsIRequest* aRequest, michael@0: nsIInputStream* aStream, michael@0: uint32_t aCount); michael@0: nsresult OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags); michael@0: michael@0: // Opens the channel, using an HTTP byte range request to start at mOffset michael@0: // if possible. Main thread only. michael@0: nsresult OpenChannel(nsIStreamListener** aStreamListener); michael@0: nsresult RecreateChannel(); michael@0: // Add headers to HTTP request. Main thread only. michael@0: void SetupChannelHeaders(); michael@0: // Closes the channel. Main thread only. michael@0: void CloseChannel(); michael@0: michael@0: // Parses 'Content-Range' header and returns results via parameters. michael@0: // Returns error if header is not available, values are not parse-able or michael@0: // values are out of range. michael@0: nsresult ParseContentRangeHeader(nsIHttpChannel * aHttpChan, michael@0: int64_t& aRangeStart, michael@0: int64_t& aRangeEnd, michael@0: int64_t& aRangeTotal); michael@0: michael@0: void DoNotifyDataReceived(); michael@0: michael@0: static NS_METHOD CopySegmentToCache(nsIInputStream *aInStream, michael@0: void *aClosure, michael@0: const char *aFromSegment, michael@0: uint32_t aToOffset, michael@0: uint32_t aCount, michael@0: uint32_t *aWriteCount); michael@0: michael@0: // Suspend the channel only if the channels is currently downloading data. michael@0: // If it isn't we set a flag, mIgnoreResume, so that PossiblyResume knows michael@0: // whether to acutually resume or not. michael@0: void PossiblySuspend(); michael@0: michael@0: // Resume from a suspend if we actually suspended (See PossiblySuspend). michael@0: void PossiblyResume(); michael@0: michael@0: // Main thread access only michael@0: int64_t mOffset; michael@0: nsRefPtr mListener; michael@0: // A data received event for the decoder that has been dispatched but has michael@0: // not yet been processed. michael@0: nsRevocableEventPtr > mDataReceivedEvent; michael@0: uint32_t mSuspendCount; michael@0: // When this flag is set, if we get a network error we should silently michael@0: // reopen the stream. michael@0: bool mReopenOnError; michael@0: // When this flag is set, we should not report the next close of the michael@0: // channel. michael@0: bool mIgnoreClose; michael@0: michael@0: // Any thread access michael@0: MediaCacheStream mCacheStream; michael@0: michael@0: // This lock protects mChannelStatistics michael@0: Mutex mLock; michael@0: nsRefPtr mChannelStatistics; michael@0: michael@0: // True if we couldn't suspend the stream and we therefore don't want michael@0: // to resume later. This is usually due to the channel not being in the michael@0: // isPending state at the time of the suspend request. michael@0: bool mIgnoreResume; michael@0: michael@0: // True if we are seeking to get the real duration of the file. michael@0: bool mSeekingForMetadata; michael@0: michael@0: // Start and end offset of the bytes to be requested. michael@0: MediaByteRange mByteRange; michael@0: michael@0: // True if the stream can seek into unbuffered ranged, i.e. if the michael@0: // connection supports byte range requests. michael@0: bool mIsTransportSeekable; michael@0: }; michael@0: michael@0: } // namespace mozilla michael@0: michael@0: #endif