/* 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/. */ #ifndef mozilla_dom_media_ChannelMediaResource_h #define mozilla_dom_media_ChannelMediaResource_h #include "BaseMediaResource.h" #include "MediaCache.h" #include "mozilla/Mutex.h" #include "nsIChannelEventSink.h" #include "nsIInterfaceRequestor.h" #include "nsIThreadRetargetableStreamListener.h" class nsIHttpChannel; namespace mozilla { /** * This class is responsible for managing the suspend count and report suspend * status of channel. **/ class ChannelSuspendAgent { public: explicit ChannelSuspendAgent(MediaCacheStream& aCacheStream) : mCacheStream(aCacheStream) {} // 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(); // Tell the agent to manage the suspend status of the channel. void Delegate(nsIChannel* aChannel); // Stop the management of the suspend status of the channel. void Revoke(); private: // Only suspends channel but not changes the suspend count. void SuspendInternal(); nsIChannel* mChannel = nullptr; MediaCacheStream& mCacheStream; uint32_t mSuspendCount = 0; bool mIsChannelSuspended = false; }; DDLoggedTypeDeclNameAndBase(ChannelMediaResource, BaseMediaResource); /** * 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 DecoderDoctorLifeLogger { // Store information shared among resources. Main thread only. struct SharedInfo { NS_INLINE_DECL_REFCOUNTING(SharedInfo); nsTArray mResources; // Null if there is not yet any data from any origin. nsCOMPtr mPrincipal; // Meaningful only when mPrincipal is non-null, // unaffected by intermediate cross-origin redirects. bool mFinalResponsesAreOpaque = false; bool mHadCrossOriginRedirects = false; private: ~SharedInfo() = default; }; RefPtr mSharedInfo; public: ChannelMediaResource(MediaResourceCallback* aDecoder, nsIChannel* aChannel, nsIURI* aURI, int64_t aStreamLength, bool aIsPrivateBrowsing = false); ~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(bool aSuspended); // 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. void CacheClientSeek(int64_t aOffset, bool aResume); // Suspend the current load since data is currently not wanted void CacheClientSuspend(); // Resume the current load since data is wanted again void CacheClientResume(); bool IsSuspended(); void ThrottleReadahead(bool bThrottle) override; // Main thread nsresult Open(nsIStreamListener** aStreamListener) override; RefPtr Close() override; void Suspend(bool aCloseImmediately) override; void Resume() override; already_AddRefed GetCurrentPrincipal() override; bool HadCrossOriginRedirects() override; bool CanClone() override; already_AddRefed CloneData( MediaResourceCallback* aDecoder) override; nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) 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; // Data stored in IO&lock-encumbered MediaCacheStream, caching recommended. bool ShouldCacheReads() override { return true; } // 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 IsTransportSeekable() override; bool IsLiveStream() const override { return mIsLiveStream; } size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { // Might be useful to track in the future: // - mListener (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); } void GetDebugInfo(dom::MediaResourceDebugInfo& aInfo) override; class Listener final : public nsIInterfaceRequestor, public nsIChannelEventSink, public nsIThreadRetargetableStreamListener, public SingleWriterLockOwner { ~Listener() = default; public: Listener(ChannelMediaResource* aResource, int64_t aOffset, uint32_t aLoadID) : mMutex("Listener.mMutex", this), mResource(aResource), mOffset(aOffset), mLoadID(aLoadID) {} NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSICHANNELEVENTSINK NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER void Revoke(); bool OnWritingThread() const override { return NS_IsMainThread(); } private: MutexSingleWriter mMutex; // mResource should only be modified on the main thread with the lock. // So it can be read without lock on the main thread or on other threads // with the lock. RefPtr mResource MOZ_GUARDED_BY(mMutex); const int64_t mOffset; const uint32_t mLoadID; }; friend class Listener; nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override; protected: nsresult Seek(int64_t aOffset, bool aResume); // These are called on the main thread by Listener. nsresult OnStartRequest(nsIRequest* aRequest, int64_t aRequestOffset); nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus); nsresult OnDataAvailable(uint32_t aLoadID, nsIInputStream* aStream, uint32_t aCount); nsresult OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags, int64_t aOffset); // Use only before MediaDecoder shutdown. Main thread only. dom::HTMLMediaElement* MediaElement() const; // Opens the channel, using an HTTP byte range request to start at aOffset // if possible. Main thread only. nsresult OpenChannel(int64_t aOffset); nsresult RecreateChannel(); // Add headers to HTTP request. Main thread only. nsresult SetupChannelHeaders(int64_t aOffset); // Closes the channel. Main thread only. void CloseChannel(); // Update the principal for the resource. Main thread only. void UpdatePrincipal(); // 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) const; // Calculates the length of the resource using HTTP headers, if this // is an HTTP channel. Returns -1 on failure, or for non HTTP channels. int64_t CalculateStreamLength() const; struct Closure { uint32_t mLoadID; ChannelMediaResource* mResource; }; static nsresult CopySegmentToCache(nsIInputStream* aInStream, void* aClosure, const char* aFromSegment, uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount); // Main thread access only // True if Close() has been called. bool mClosed = false; // The last reported seekability state for the underlying channel bool mIsTransportSeekable = false; // Length of the content first reported. int64_t mFirstReadLength = -1; RefPtr mListener; // A mono-increasing integer to uniquely identify the channel we are loading. uint32_t mLoadID = 0; bool mIsLiveStream = false; // Any thread access MediaCacheStream mCacheStream; ChannelSuspendAgent mSuspendAgent; // The size of the stream if known at construction time (such as with blob) const int64_t mKnownStreamLength; }; } // namespace mozilla #endif // mozilla_dom_media_ChannelMediaResource_h