1 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla code.
16 : *
17 : * The Initial Developer of the Original Code is the Mozilla Corporation.
18 : * Portions created by the Initial Developer are Copyright (C) 2007
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Chris Double <chris.double@double.co.nz>
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #if !defined(MediaResource_h_)
39 : #define MediaResource_h_
40 :
41 : #include "mozilla/Mutex.h"
42 : #include "mozilla/XPCOM.h"
43 : #include "nsIChannel.h"
44 : #include "nsIPrincipal.h"
45 : #include "nsIURI.h"
46 : #include "nsIStreamListener.h"
47 : #include "nsIChannelEventSink.h"
48 : #include "nsIInterfaceRequestor.h"
49 : #include "nsMediaCache.h"
50 :
51 : // For HTTP seeking, if number of bytes needing to be
52 : // seeked forward is less than this value then a read is
53 : // done rather than a byte range request.
54 : static const PRInt64 SEEK_VS_READ_THRESHOLD = 32*1024;
55 :
56 : static const PRUint32 HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE = 416;
57 :
58 : class nsMediaDecoder;
59 :
60 : namespace mozilla {
61 :
62 : /**
63 : * This class is useful for estimating rates of data passing through
64 : * some channel. The idea is that activity on the channel "starts"
65 : * and "stops" over time. At certain times data passes through the
66 : * channel (usually while the channel is active; data passing through
67 : * an inactive channel is ignored). The GetRate() function computes
68 : * an estimate of the "current rate" of the channel, which is some
69 : * kind of average of the data passing through over the time the
70 : * channel is active.
71 : *
72 : * All methods take "now" as a parameter so the user of this class can
73 : * control the timeline used.
74 : */
75 : class MediaChannelStatistics {
76 : public:
77 : MediaChannelStatistics() { Reset(); }
78 : void Reset() {
79 : mLastStartTime = TimeStamp();
80 : mAccumulatedTime = TimeDuration(0);
81 : mAccumulatedBytes = 0;
82 : mIsStarted = false;
83 : }
84 : void Start(TimeStamp aNow) {
85 : if (mIsStarted)
86 : return;
87 : mLastStartTime = aNow;
88 : mIsStarted = true;
89 : }
90 : void Stop(TimeStamp aNow) {
91 : if (!mIsStarted)
92 : return;
93 : mAccumulatedTime += aNow - mLastStartTime;
94 : mIsStarted = false;
95 : }
96 : void AddBytes(PRInt64 aBytes) {
97 : if (!mIsStarted) {
98 : // ignore this data, it may be related to seeking or some other
99 : // operation we don't care about
100 : return;
101 : }
102 : mAccumulatedBytes += aBytes;
103 : }
104 : double GetRateAtLastStop(bool* aReliable) {
105 : double seconds = mAccumulatedTime.ToSeconds();
106 : *aReliable = seconds >= 1.0;
107 : if (seconds <= 0.0)
108 : return 0.0;
109 : return static_cast<double>(mAccumulatedBytes)/seconds;
110 : }
111 : double GetRate(TimeStamp aNow, bool* aReliable) {
112 : TimeDuration time = mAccumulatedTime;
113 : if (mIsStarted) {
114 : time += aNow - mLastStartTime;
115 : }
116 : double seconds = time.ToSeconds();
117 : *aReliable = seconds >= 3.0;
118 : if (seconds <= 0.0)
119 : return 0.0;
120 : return static_cast<double>(mAccumulatedBytes)/seconds;
121 : }
122 : private:
123 : PRInt64 mAccumulatedBytes;
124 : TimeDuration mAccumulatedTime;
125 : TimeStamp mLastStartTime;
126 : bool mIsStarted;
127 : };
128 :
129 : // Represents a section of contiguous media, with a start and end offset.
130 : // Used to denote ranges of data which are cached.
131 0 : class MediaByteRange {
132 : public:
133 : MediaByteRange() : mStart(0), mEnd(0) {}
134 :
135 : MediaByteRange(PRInt64 aStart, PRInt64 aEnd)
136 : : mStart(aStart), mEnd(aEnd)
137 : {
138 : NS_ASSERTION(mStart < mEnd, "Range should end after start!");
139 : }
140 :
141 : bool IsNull() const {
142 : return mStart == 0 && mEnd == 0;
143 : }
144 :
145 : PRInt64 mStart, mEnd;
146 : };
147 :
148 : /**
149 : * Provides a thread-safe, seek/read interface to resources
150 : * loaded from a URI. Uses nsMediaCache to cache data received over
151 : * Necko's async channel API, thus resolving the mismatch between clients
152 : * that need efficient random access to the data and protocols that do not
153 : * support efficient random access, such as HTTP.
154 : *
155 : * Instances of this class must be created on the main thread.
156 : * Most methods must be called on the main thread only. Read, Seek and
157 : * Tell must only be called on non-main threads. In the case of the Ogg
158 : * Decoder they are called on the Decode thread for example. You must
159 : * ensure that no threads are calling these methods once Close is called.
160 : *
161 : * Instances of this class are explicitly managed. 'delete' it when done.
162 : *
163 : * The generic implementation of this class is ChannelMediaResource, which can
164 : * handle any URI for which Necko supports AsyncOpen.
165 : * The 'file:' protocol can be implemented efficiently with direct random
166 : * access, so the FileMediaResource implementation class bypasses the cache.
167 : * MediaResource::Create automatically chooses the best implementation class.
168 : */
169 : class MediaResource
170 : {
171 : public:
172 : virtual ~MediaResource()
173 : {
174 : MOZ_COUNT_DTOR(MediaResource);
175 : }
176 :
177 : // The following can be called on the main thread only:
178 : // Get the URI
179 : nsIURI* URI() const { return mURI; }
180 : // Close the resource, stop any listeners, channels, etc.
181 : // Cancels any currently blocking Read request and forces that request to
182 : // return an error.
183 : virtual nsresult Close() = 0;
184 : // Suspend any downloads that are in progress.
185 : // If aCloseImmediately is set, resources should be released immediately
186 : // since we don't expect to resume again any time soon. Otherwise we
187 : // may resume again soon so resources should be held for a little
188 : // while.
189 : virtual void Suspend(bool aCloseImmediately) = 0;
190 : // Resume any downloads that have been suspended.
191 : virtual void Resume() = 0;
192 : // Get the current principal for the channel
193 : virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
194 : // If this returns false, then we shouldn't try to clone this MediaResource
195 : // because its underlying resources are not suitable for reuse (e.g.
196 : // because the underlying connection has been lost, or this resource
197 : // just can't be safely cloned). If this returns true, CloneData could
198 : // still fail. If this returns false, CloneData should not be called.
199 : virtual bool CanClone() { return false; }
200 : // Create a new stream of the same type that refers to the same URI
201 : // with a new channel. Any cached data associated with the original
202 : // stream should be accessible in the new stream too.
203 : virtual MediaResource* CloneData(nsMediaDecoder* aDecoder) = 0;
204 :
205 : // These methods are called off the main thread.
206 : // The mode is initially MODE_PLAYBACK.
207 : virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode) = 0;
208 : // This is the client's estimate of the playback rate assuming
209 : // the media plays continuously. The cache can't guess this itself
210 : // because it doesn't know when the decoder was paused, buffering, etc.
211 : virtual void SetPlaybackRate(PRUint32 aBytesPerSecond) = 0;
212 : // Read up to aCount bytes from the stream. The buffer must have
213 : // enough room for at least aCount bytes. Stores the number of
214 : // actual bytes read in aBytes (0 on end of file).
215 : // May read less than aCount bytes if the number of
216 : // available bytes is less than aCount. Always check *aBytes after
217 : // read, and call again if necessary.
218 : virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes) = 0;
219 : // Seek to the given bytes offset in the stream. aWhence can be
220 : // one of:
221 : // NS_SEEK_SET
222 : // NS_SEEK_CUR
223 : // NS_SEEK_END
224 : //
225 : // In the Http strategy case the cancel will cause the http
226 : // channel's listener to close the pipe, forcing an i/o error on any
227 : // blocked read. This will allow the decode thread to complete the
228 : // event.
229 : //
230 : // In the case of a seek in progress, the byte range request creates
231 : // a new listener. This is done on the main thread via seek
232 : // synchronously dispatching an event. This avoids the issue of us
233 : // closing the listener but an outstanding byte range request
234 : // creating a new one. They run on the same thread so no explicit
235 : // synchronisation is required. The byte range request checks for
236 : // the cancel flag and does not create a new channel or listener if
237 : // we are cancelling.
238 : //
239 : // The default strategy does not do any seeking - the only issue is
240 : // a blocked read which it handles by causing the listener to close
241 : // the pipe, as per the http case.
242 : //
243 : // The file strategy doesn't block for any great length of time so
244 : // is fine for a no-op cancel.
245 : virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset) = 0;
246 : // Report the current offset in bytes from the start of the stream.
247 : virtual PRInt64 Tell() = 0;
248 : // Moves any existing channel loads into the background, so that they don't
249 : // block the load event. Any new loads initiated (for example to seek)
250 : // will also be in the background.
251 : void MoveLoadsToBackground();
252 : // Ensures that the value returned by IsSuspendedByCache below is up to date
253 : // (i.e. the cache has examined this stream at least once).
254 : virtual void EnsureCacheUpToDate() {}
255 :
256 : // These can be called on any thread.
257 : // Cached blocks associated with this stream will not be evicted
258 : // while the stream is pinned.
259 : virtual void Pin() = 0;
260 : virtual void Unpin() = 0;
261 : // Get the estimated download rate in bytes per second (assuming no
262 : // pausing of the channel is requested by Gecko).
263 : // *aIsReliable is set to true if we think the estimate is useful.
264 : virtual double GetDownloadRate(bool* aIsReliable) = 0;
265 : // Get the length of the stream in bytes. Returns -1 if not known.
266 : // This can change over time; after a seek operation, a misbehaving
267 : // server may give us a resource of a different length to what it had
268 : // reported previously --- or it may just lie in its Content-Length
269 : // header and give us more or less data than it reported. We will adjust
270 : // the result of GetLength to reflect the data that's actually arriving.
271 : virtual PRInt64 GetLength() = 0;
272 : // Returns the offset of the first byte of cached data at or after aOffset,
273 : // or -1 if there is no such cached data.
274 : virtual PRInt64 GetNextCachedData(PRInt64 aOffset) = 0;
275 : // Returns the end of the bytes starting at the given offset
276 : // which are in cache.
277 : virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset) = 0;
278 : // Returns true if all the data from aOffset to the end of the stream
279 : // is in cache. If the end of the stream is not known, we return false.
280 : virtual bool IsDataCachedToEndOfResource(PRInt64 aOffset) = 0;
281 : // Returns true if this stream is suspended by the cache because the
282 : // cache is full. If true then the decoder should try to start consuming
283 : // data, otherwise we may not be able to make progress.
284 : // nsMediaDecoder::NotifySuspendedStatusChanged is called when this
285 : // changes.
286 : // For resources using the media cache, this returns true only when all
287 : // streams for the same resource are all suspended.
288 : // If aActiveResource is non-null, fills it with a pointer to a stream
289 : // for this resource that is not suspended or ended.
290 : virtual bool IsSuspendedByCache(MediaResource** aActiveResource) = 0;
291 : // Returns true if this stream has been suspended.
292 : virtual bool IsSuspended() = 0;
293 : // Reads only data which is cached in the media cache. If you try to read
294 : // any data which overlaps uncached data, or if aCount bytes otherwise can't
295 : // be read, this function will return failure. This function be called from
296 : // any thread, and it is the only read operation which is safe to call on
297 : // the main thread, since it's guaranteed to be non blocking.
298 : virtual nsresult ReadFromCache(char* aBuffer,
299 : PRInt64 aOffset,
300 : PRUint32 aCount) = 0;
301 :
302 : /**
303 : * Create a resource, reading data from the channel. Call on main thread only.
304 : * The caller must follow up by calling resource->Open().
305 : */
306 : static MediaResource* Create(nsMediaDecoder* aDecoder, nsIChannel* aChannel);
307 :
308 : /**
309 : * Open the stream. This creates a stream listener and returns it in
310 : * aStreamListener; this listener needs to be notified of incoming data.
311 : */
312 : virtual nsresult Open(nsIStreamListener** aStreamListener) = 0;
313 :
314 : /**
315 : * Fills aRanges with MediaByteRanges representing the data which is cached
316 : * in the media cache. Stream should be pinned during call and while
317 : * aRanges is being used.
318 : */
319 : virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges) = 0;
320 :
321 : protected:
322 : MediaResource(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
323 : mDecoder(aDecoder),
324 : mChannel(aChannel),
325 : mURI(aURI),
326 : mLoadInBackground(false)
327 : {
328 : MOZ_COUNT_CTOR(MediaResource);
329 : }
330 :
331 : // Set the request's load flags to aFlags. If the request is part of a
332 : // load group, the request is removed from the group, the flags are set, and
333 : // then the request is added back to the load group.
334 : void ModifyLoadFlags(nsLoadFlags aFlags);
335 :
336 : // This is not an nsCOMPointer to prevent a circular reference
337 : // between the decoder to the media stream object. The stream never
338 : // outlives the lifetime of the decoder.
339 : nsMediaDecoder* mDecoder;
340 :
341 : // Channel used to download the media data. Must be accessed
342 : // from the main thread only.
343 : nsCOMPtr<nsIChannel> mChannel;
344 :
345 : // URI in case the stream needs to be re-opened. Access from
346 : // main thread only.
347 : nsCOMPtr<nsIURI> mURI;
348 :
349 : // True if MoveLoadsToBackground() has been called, i.e. the load event
350 : // has been fired, and all channel loads will be in the background.
351 : bool mLoadInBackground;
352 : };
353 :
354 : /**
355 : * This is the MediaResource implementation that wraps Necko channels.
356 : * Much of its functionality is actually delegated to nsMediaCache via
357 : * an underlying nsMediaCacheStream.
358 : *
359 : * All synchronization is performed by nsMediaCacheStream; all off-main-
360 : * thread operations are delegated directly to that object.
361 : */
362 : class ChannelMediaResource : public MediaResource
363 : {
364 : public:
365 : ChannelMediaResource(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI);
366 : ~ChannelMediaResource();
367 :
368 : // These are called on the main thread by nsMediaCache. These must
369 : // not block or grab locks, because the media cache is holding its lock.
370 : // Notify that data is available from the cache. This can happen even
371 : // if this stream didn't read any data, since another stream might have
372 : // received data for the same resource.
373 : void CacheClientNotifyDataReceived();
374 : // Notify that we reached the end of the stream. This can happen even
375 : // if this stream didn't read any data, since another stream might have
376 : // received data for the same resource.
377 : void CacheClientNotifyDataEnded(nsresult aStatus);
378 :
379 : // These are called on the main thread by nsMediaCache. These shouldn't block,
380 : // but they may grab locks --- the media cache is not holding its lock
381 : // when these are called.
382 : // Start a new load at the given aOffset. The old load is cancelled
383 : // and no more data from the old load will be notified via
384 : // nsMediaCacheStream::NotifyDataReceived/Ended.
385 : // This can fail.
386 : nsresult CacheClientSeek(PRInt64 aOffset, bool aResume);
387 : // Suspend the current load since data is currently not wanted
388 : nsresult CacheClientSuspend();
389 : // Resume the current load since data is wanted again
390 : nsresult CacheClientResume();
391 :
392 : // Main thread
393 : virtual nsresult Open(nsIStreamListener** aStreamListener);
394 : virtual nsresult Close();
395 : virtual void Suspend(bool aCloseImmediately);
396 : virtual void Resume();
397 : virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
398 : // Return true if the stream has been closed.
399 : bool IsClosed() const { return mCacheStream.IsClosed(); }
400 : virtual bool CanClone();
401 : virtual MediaResource* CloneData(nsMediaDecoder* aDecoder);
402 : virtual nsresult ReadFromCache(char* aBuffer, PRInt64 aOffset, PRUint32 aCount);
403 : virtual void EnsureCacheUpToDate();
404 :
405 : // Other thread
406 : virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode);
407 : virtual void SetPlaybackRate(PRUint32 aBytesPerSecond);
408 : virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
409 : virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
410 : virtual PRInt64 Tell();
411 :
412 : // Any thread
413 : virtual void Pin();
414 : virtual void Unpin();
415 : virtual double GetDownloadRate(bool* aIsReliable);
416 : virtual PRInt64 GetLength();
417 : virtual PRInt64 GetNextCachedData(PRInt64 aOffset);
418 : virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset);
419 : virtual bool IsDataCachedToEndOfResource(PRInt64 aOffset);
420 : virtual bool IsSuspendedByCache(MediaResource** aActiveResource);
421 : virtual bool IsSuspended();
422 :
423 : class Listener : public nsIStreamListener,
424 : public nsIInterfaceRequestor,
425 : public nsIChannelEventSink
426 : {
427 : public:
428 : Listener(ChannelMediaResource* aResource) : mResource(aResource) {}
429 :
430 : NS_DECL_ISUPPORTS
431 : NS_DECL_NSIREQUESTOBSERVER
432 : NS_DECL_NSISTREAMLISTENER
433 : NS_DECL_NSICHANNELEVENTSINK
434 : NS_DECL_NSIINTERFACEREQUESTOR
435 :
436 : void Revoke() { mResource = nsnull; }
437 :
438 : private:
439 : ChannelMediaResource* mResource;
440 : };
441 : friend class Listener;
442 :
443 : nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges);
444 :
445 : protected:
446 : // These are called on the main thread by Listener.
447 : nsresult OnStartRequest(nsIRequest* aRequest);
448 : nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);
449 : nsresult OnDataAvailable(nsIRequest* aRequest,
450 : nsIInputStream* aStream,
451 : PRUint32 aCount);
452 : nsresult OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew, PRUint32 aFlags);
453 :
454 : // Opens the channel, using an HTTP byte range request to start at mOffset
455 : // if possible. Main thread only.
456 : nsresult OpenChannel(nsIStreamListener** aStreamListener);
457 : nsresult RecreateChannel();
458 : // Add headers to HTTP request. Main thread only.
459 : void SetupChannelHeaders();
460 : // Closes the channel. Main thread only.
461 : void CloseChannel();
462 :
463 : void DoNotifyDataReceived();
464 :
465 : static NS_METHOD CopySegmentToCache(nsIInputStream *aInStream,
466 : void *aClosure,
467 : const char *aFromSegment,
468 : PRUint32 aToOffset,
469 : PRUint32 aCount,
470 : PRUint32 *aWriteCount);
471 :
472 : // Suspend the channel only if the channels is currently downloading data.
473 : // If it isn't we set a flag, mIgnoreResume, so that PossiblyResume knows
474 : // whether to acutually resume or not.
475 : void PossiblySuspend();
476 :
477 : // Resume from a suspend if we actually suspended (See PossiblySuspend).
478 : void PossiblyResume();
479 :
480 : // Main thread access only
481 : PRInt64 mOffset;
482 : nsRefPtr<Listener> mListener;
483 : // A data received event for the decoder that has been dispatched but has
484 : // not yet been processed.
485 : nsRevocableEventPtr<nsRunnableMethod<ChannelMediaResource, void, false> > mDataReceivedEvent;
486 : PRUint32 mSuspendCount;
487 : // When this flag is set, if we get a network error we should silently
488 : // reopen the stream.
489 : bool mReopenOnError;
490 : // When this flag is set, we should not report the next close of the
491 : // channel.
492 : bool mIgnoreClose;
493 :
494 : // Any thread access
495 : nsMediaCacheStream mCacheStream;
496 :
497 : // This lock protects mChannelStatistics
498 : Mutex mLock;
499 : MediaChannelStatistics mChannelStatistics;
500 :
501 : // True if we couldn't suspend the stream and we therefore don't want
502 : // to resume later. This is usually due to the channel not being in the
503 : // isPending state at the time of the suspend request.
504 : bool mIgnoreResume;
505 : };
506 :
507 : }
508 :
509 : #endif
|