Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | /* |
michael@0 | 7 | Each video element based on MediaDecoder has a state machine to manage |
michael@0 | 8 | its play state and keep the current frame up to date. All state machines |
michael@0 | 9 | share time in a single shared thread. Each decoder also has one thread |
michael@0 | 10 | dedicated to decoding audio and video data. This thread is shutdown when |
michael@0 | 11 | playback is paused. Each decoder also has a thread to push decoded audio |
michael@0 | 12 | to the hardware. This thread is not created until playback starts, but |
michael@0 | 13 | currently is not destroyed when paused, only when playback ends. |
michael@0 | 14 | |
michael@0 | 15 | The decoder owns the resources for downloading the media file, and the |
michael@0 | 16 | high level state. It holds an owning reference to the state machine that |
michael@0 | 17 | owns all the resources related to decoding data, and manages the low level |
michael@0 | 18 | decoding operations and A/V sync. |
michael@0 | 19 | |
michael@0 | 20 | Each state machine runs on the shared state machine thread. Every time some |
michael@0 | 21 | action is required for a state machine, it is scheduled to run on the shared |
michael@0 | 22 | the state machine thread. The state machine runs one "cycle" on the state |
michael@0 | 23 | machine thread, and then returns. If necessary, it will schedule itself to |
michael@0 | 24 | run again in future. While running this cycle, it must not block the |
michael@0 | 25 | thread, as other state machines' events may need to run. State shared |
michael@0 | 26 | between a state machine's threads is synchronised via the monitor owned |
michael@0 | 27 | by its MediaDecoder object. |
michael@0 | 28 | |
michael@0 | 29 | The Main thread controls the decode state machine by setting the value |
michael@0 | 30 | of a mPlayState variable and notifying on the monitor based on the |
michael@0 | 31 | high level player actions required (Seek, Pause, Play, etc). |
michael@0 | 32 | |
michael@0 | 33 | The player states are the states requested by the client through the |
michael@0 | 34 | DOM API. They represent the desired state of the player, while the |
michael@0 | 35 | decoder's state represents the actual state of the decoder. |
michael@0 | 36 | |
michael@0 | 37 | The high level state of the player is maintained via a PlayState value. |
michael@0 | 38 | It can have the following states: |
michael@0 | 39 | |
michael@0 | 40 | START |
michael@0 | 41 | The decoder has been initialized but has no resource loaded. |
michael@0 | 42 | PAUSED |
michael@0 | 43 | A request via the API has been received to pause playback. |
michael@0 | 44 | LOADING |
michael@0 | 45 | A request via the API has been received to load a resource. |
michael@0 | 46 | PLAYING |
michael@0 | 47 | A request via the API has been received to start playback. |
michael@0 | 48 | SEEKING |
michael@0 | 49 | A request via the API has been received to start seeking. |
michael@0 | 50 | COMPLETED |
michael@0 | 51 | Playback has completed. |
michael@0 | 52 | SHUTDOWN |
michael@0 | 53 | The decoder is about to be destroyed. |
michael@0 | 54 | |
michael@0 | 55 | State transition occurs when the Media Element calls the Play, Seek, |
michael@0 | 56 | etc methods on the MediaDecoder object. When the transition |
michael@0 | 57 | occurs MediaDecoder then calls the methods on the decoder state |
michael@0 | 58 | machine object to cause it to behave as required by the play state. |
michael@0 | 59 | State transitions will likely schedule the state machine to run to |
michael@0 | 60 | affect the change. |
michael@0 | 61 | |
michael@0 | 62 | An implementation of the MediaDecoderStateMachine class is the event |
michael@0 | 63 | that gets dispatched to the state machine thread. Each time the event is run, |
michael@0 | 64 | the state machine must cycle the state machine once, and then return. |
michael@0 | 65 | |
michael@0 | 66 | The state machine has the following states: |
michael@0 | 67 | |
michael@0 | 68 | DECODING_METADATA |
michael@0 | 69 | The media headers are being loaded, and things like framerate, etc are |
michael@0 | 70 | being determined, and the first frame of audio/video data is being decoded. |
michael@0 | 71 | DECODING |
michael@0 | 72 | The decode has started. If the PlayState is PLAYING, the decode thread |
michael@0 | 73 | should be alive and decoding video and audio frame, the audio thread |
michael@0 | 74 | should be playing audio, and the state machine should run periodically |
michael@0 | 75 | to update the video frames being displayed. |
michael@0 | 76 | SEEKING |
michael@0 | 77 | A seek operation is in progress. The decode thread should be seeking. |
michael@0 | 78 | BUFFERING |
michael@0 | 79 | Decoding is paused while data is buffered for smooth playback. If playback |
michael@0 | 80 | is paused (PlayState transitions to PAUSED) we'll destory the decode thread. |
michael@0 | 81 | COMPLETED |
michael@0 | 82 | The resource has completed decoding, but possibly not finished playback. |
michael@0 | 83 | The decode thread will be destroyed. Once playback finished, the audio |
michael@0 | 84 | thread will also be destroyed. |
michael@0 | 85 | SHUTDOWN |
michael@0 | 86 | The decoder object and its state machine are about to be destroyed. |
michael@0 | 87 | Once the last state machine has been destroyed, the shared state machine |
michael@0 | 88 | thread will also be destroyed. It will be recreated later if needed. |
michael@0 | 89 | |
michael@0 | 90 | The following result in state transitions. |
michael@0 | 91 | |
michael@0 | 92 | Shutdown() |
michael@0 | 93 | Clean up any resources the MediaDecoderStateMachine owns. |
michael@0 | 94 | Play() |
michael@0 | 95 | Start decoding and playback of media data. |
michael@0 | 96 | Buffer |
michael@0 | 97 | This is not user initiated. It occurs when the |
michael@0 | 98 | available data in the stream drops below a certain point. |
michael@0 | 99 | Complete |
michael@0 | 100 | This is not user initiated. It occurs when the |
michael@0 | 101 | stream is completely decoded. |
michael@0 | 102 | Seek(double) |
michael@0 | 103 | Seek to the time position given in the resource. |
michael@0 | 104 | |
michael@0 | 105 | A state transition diagram: |
michael@0 | 106 | |
michael@0 | 107 | DECODING_METADATA |
michael@0 | 108 | | | |
michael@0 | 109 | v | Shutdown() |
michael@0 | 110 | | | |
michael@0 | 111 | v -->-------------------->--------------------------| |
michael@0 | 112 | |---------------->----->------------------------| v |
michael@0 | 113 | DECODING | | | | | |
michael@0 | 114 | ^ v Seek(t) | | | | |
michael@0 | 115 | | Play() | v | | | |
michael@0 | 116 | ^-----------<----SEEKING | v Complete v v |
michael@0 | 117 | | | | | | | |
michael@0 | 118 | | | | COMPLETED SHUTDOWN-<-| |
michael@0 | 119 | ^ ^ | |Shutdown() | |
michael@0 | 120 | | | | >-------->-----^ |
michael@0 | 121 | | Play() |Seek(t) |Buffer() | |
michael@0 | 122 | -----------<--------<-------BUFFERING | |
michael@0 | 123 | | ^ |
michael@0 | 124 | v Shutdown() | |
michael@0 | 125 | | | |
michael@0 | 126 | ------------>-----| |
michael@0 | 127 | |
michael@0 | 128 | The following represents the states that the MediaDecoder object |
michael@0 | 129 | can be in, and the valid states the MediaDecoderStateMachine can be in at that |
michael@0 | 130 | time: |
michael@0 | 131 | |
michael@0 | 132 | player LOADING decoder DECODING_METADATA |
michael@0 | 133 | player PLAYING decoder DECODING, BUFFERING, SEEKING, COMPLETED |
michael@0 | 134 | player PAUSED decoder DECODING, BUFFERING, SEEKING, COMPLETED |
michael@0 | 135 | player SEEKING decoder SEEKING |
michael@0 | 136 | player COMPLETED decoder SHUTDOWN |
michael@0 | 137 | player SHUTDOWN decoder SHUTDOWN |
michael@0 | 138 | |
michael@0 | 139 | The general sequence of events is: |
michael@0 | 140 | |
michael@0 | 141 | 1) The video element calls Load on MediaDecoder. This creates the |
michael@0 | 142 | state machine and starts the channel for downloading the |
michael@0 | 143 | file. It instantiates and schedules the MediaDecoderStateMachine. The |
michael@0 | 144 | high level LOADING state is entered, which results in the decode |
michael@0 | 145 | thread being created and starting to decode metadata. These are |
michael@0 | 146 | the headers that give the video size, framerate, etc. Load() returns |
michael@0 | 147 | immediately to the calling video element. |
michael@0 | 148 | |
michael@0 | 149 | 2) When the metadata has been loaded by the decode thread, the state machine |
michael@0 | 150 | will call a method on the video element object to inform it that this |
michael@0 | 151 | step is done, so it can do the things required by the video specification |
michael@0 | 152 | at this stage. The decode thread then continues to decode the first frame |
michael@0 | 153 | of data. |
michael@0 | 154 | |
michael@0 | 155 | 3) When the first frame of data has been successfully decoded the state |
michael@0 | 156 | machine calls a method on the video element object to inform it that |
michael@0 | 157 | this step has been done, once again so it can do the required things |
michael@0 | 158 | by the video specification at this stage. |
michael@0 | 159 | |
michael@0 | 160 | This results in the high level state changing to PLAYING or PAUSED |
michael@0 | 161 | depending on any user action that may have occurred. |
michael@0 | 162 | |
michael@0 | 163 | While the play state is PLAYING, the decode thread will decode |
michael@0 | 164 | data, and the audio thread will push audio data to the hardware to |
michael@0 | 165 | be played. The state machine will run periodically on the shared |
michael@0 | 166 | state machine thread to ensure video frames are played at the |
michael@0 | 167 | correct time; i.e. the state machine manages A/V sync. |
michael@0 | 168 | |
michael@0 | 169 | The Shutdown method on MediaDecoder closes the download channel, and |
michael@0 | 170 | signals to the state machine that it should shutdown. The state machine |
michael@0 | 171 | shuts down asynchronously, and will release the owning reference to the |
michael@0 | 172 | state machine once its threads are shutdown. |
michael@0 | 173 | |
michael@0 | 174 | The owning object of a MediaDecoder object *MUST* call Shutdown when |
michael@0 | 175 | destroying the MediaDecoder object. |
michael@0 | 176 | |
michael@0 | 177 | */ |
michael@0 | 178 | #if !defined(MediaDecoder_h_) |
michael@0 | 179 | #define MediaDecoder_h_ |
michael@0 | 180 | |
michael@0 | 181 | #include "nsISupports.h" |
michael@0 | 182 | #include "nsCOMPtr.h" |
michael@0 | 183 | #include "nsIObserver.h" |
michael@0 | 184 | #include "nsAutoPtr.h" |
michael@0 | 185 | #include "MediaResource.h" |
michael@0 | 186 | #include "mozilla/dom/AudioChannelBinding.h" |
michael@0 | 187 | #include "mozilla/gfx/Rect.h" |
michael@0 | 188 | #include "mozilla/ReentrantMonitor.h" |
michael@0 | 189 | #include "mozilla/TimeStamp.h" |
michael@0 | 190 | #include "MediaStreamGraph.h" |
michael@0 | 191 | #include "AbstractMediaDecoder.h" |
michael@0 | 192 | #include "necko-config.h" |
michael@0 | 193 | |
michael@0 | 194 | class nsIStreamListener; |
michael@0 | 195 | class nsIPrincipal; |
michael@0 | 196 | class nsITimer; |
michael@0 | 197 | |
michael@0 | 198 | namespace mozilla { |
michael@0 | 199 | namespace dom { |
michael@0 | 200 | class TimeRanges; |
michael@0 | 201 | } |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | namespace mozilla { |
michael@0 | 205 | namespace layers { |
michael@0 | 206 | class Image; |
michael@0 | 207 | } //namespace layers |
michael@0 | 208 | |
michael@0 | 209 | class VideoFrameContainer; |
michael@0 | 210 | class MediaDecoderStateMachine; |
michael@0 | 211 | class MediaDecoderOwner; |
michael@0 | 212 | |
michael@0 | 213 | // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to |
michael@0 | 214 | // GetTickCount() and conflicts with MediaDecoder::GetCurrentTime implementation. |
michael@0 | 215 | #ifdef GetCurrentTime |
michael@0 | 216 | #undef GetCurrentTime |
michael@0 | 217 | #endif |
michael@0 | 218 | |
michael@0 | 219 | // Stores the seek target; the time to seek to, and whether an Accurate, |
michael@0 | 220 | // or "Fast" (nearest keyframe) seek was requested. |
michael@0 | 221 | struct SeekTarget { |
michael@0 | 222 | enum Type { |
michael@0 | 223 | Invalid, |
michael@0 | 224 | PrevSyncPoint, |
michael@0 | 225 | Accurate |
michael@0 | 226 | }; |
michael@0 | 227 | SeekTarget() |
michael@0 | 228 | : mTime(-1.0) |
michael@0 | 229 | , mType(SeekTarget::Invalid) |
michael@0 | 230 | { |
michael@0 | 231 | } |
michael@0 | 232 | SeekTarget(int64_t aTimeUsecs, Type aType) |
michael@0 | 233 | : mTime(aTimeUsecs) |
michael@0 | 234 | , mType(aType) |
michael@0 | 235 | { |
michael@0 | 236 | } |
michael@0 | 237 | bool IsValid() const { |
michael@0 | 238 | return mType != SeekTarget::Invalid; |
michael@0 | 239 | } |
michael@0 | 240 | void Reset() { |
michael@0 | 241 | mTime = -1; |
michael@0 | 242 | mType = SeekTarget::Invalid; |
michael@0 | 243 | } |
michael@0 | 244 | // Seek target time in microseconds. |
michael@0 | 245 | int64_t mTime; |
michael@0 | 246 | // Whether we should seek "Fast", or "Accurate". |
michael@0 | 247 | // "Fast" seeks to the seek point preceeding mTime, whereas |
michael@0 | 248 | // "Accurate" seeks as close as possible to mTime. |
michael@0 | 249 | Type mType; |
michael@0 | 250 | }; |
michael@0 | 251 | |
michael@0 | 252 | class MediaDecoder : public nsIObserver, |
michael@0 | 253 | public AbstractMediaDecoder |
michael@0 | 254 | { |
michael@0 | 255 | public: |
michael@0 | 256 | class DecodedStreamGraphListener; |
michael@0 | 257 | |
michael@0 | 258 | NS_DECL_THREADSAFE_ISUPPORTS |
michael@0 | 259 | NS_DECL_NSIOBSERVER |
michael@0 | 260 | |
michael@0 | 261 | // Enumeration for the valid play states (see mPlayState) |
michael@0 | 262 | enum PlayState { |
michael@0 | 263 | PLAY_STATE_START, |
michael@0 | 264 | PLAY_STATE_LOADING, |
michael@0 | 265 | PLAY_STATE_PAUSED, |
michael@0 | 266 | PLAY_STATE_PLAYING, |
michael@0 | 267 | PLAY_STATE_SEEKING, |
michael@0 | 268 | PLAY_STATE_ENDED, |
michael@0 | 269 | PLAY_STATE_SHUTDOWN |
michael@0 | 270 | }; |
michael@0 | 271 | |
michael@0 | 272 | MediaDecoder(); |
michael@0 | 273 | virtual ~MediaDecoder(); |
michael@0 | 274 | |
michael@0 | 275 | // Reset the decoder and notify the media element that |
michael@0 | 276 | // server connection is closed. |
michael@0 | 277 | virtual void ResetConnectionState(); |
michael@0 | 278 | // Create a new decoder of the same type as this one. |
michael@0 | 279 | // Subclasses must implement this. |
michael@0 | 280 | virtual MediaDecoder* Clone() = 0; |
michael@0 | 281 | // Create a new state machine to run this decoder. |
michael@0 | 282 | // Subclasses must implement this. |
michael@0 | 283 | virtual MediaDecoderStateMachine* CreateStateMachine() = 0; |
michael@0 | 284 | |
michael@0 | 285 | // Call on the main thread only. |
michael@0 | 286 | // Perform any initialization required for the decoder. |
michael@0 | 287 | // Return true on successful initialisation, false |
michael@0 | 288 | // on failure. |
michael@0 | 289 | virtual bool Init(MediaDecoderOwner* aOwner); |
michael@0 | 290 | |
michael@0 | 291 | // Cleanup internal data structures. Must be called on the main |
michael@0 | 292 | // thread by the owning object before that object disposes of this object. |
michael@0 | 293 | virtual void Shutdown(); |
michael@0 | 294 | |
michael@0 | 295 | // Start downloading the media. Decode the downloaded data up to the |
michael@0 | 296 | // point of the first frame of data. |
michael@0 | 297 | // This is called at most once per decoder, after Init(). |
michael@0 | 298 | virtual nsresult Load(nsIStreamListener** aListener, |
michael@0 | 299 | MediaDecoder* aCloneDonor); |
michael@0 | 300 | |
michael@0 | 301 | // Called in |Load| to open mResource. |
michael@0 | 302 | nsresult OpenResource(nsIStreamListener** aStreamListener); |
michael@0 | 303 | |
michael@0 | 304 | // Called when the video file has completed downloading. |
michael@0 | 305 | virtual void ResourceLoaded(); |
michael@0 | 306 | |
michael@0 | 307 | // Called if the media file encounters a network error. |
michael@0 | 308 | virtual void NetworkError(); |
michael@0 | 309 | |
michael@0 | 310 | // Get the current MediaResource being used. Its URI will be returned |
michael@0 | 311 | // by currentSrc. Returns what was passed to Load(), if Load() has been called. |
michael@0 | 312 | // Note: The MediaResource is refcounted, but it outlives the MediaDecoder, |
michael@0 | 313 | // so it's OK to use the reference returned by this function without |
michael@0 | 314 | // refcounting, *unless* you need to store and use the reference after the |
michael@0 | 315 | // MediaDecoder has been destroyed. You might need to do this if you're |
michael@0 | 316 | // wrapping the MediaResource in some kind of byte stream interface to be |
michael@0 | 317 | // passed to a platform decoder. |
michael@0 | 318 | MediaResource* GetResource() const MOZ_FINAL MOZ_OVERRIDE |
michael@0 | 319 | { |
michael@0 | 320 | return mResource; |
michael@0 | 321 | } |
michael@0 | 322 | void SetResource(MediaResource* aResource) |
michael@0 | 323 | { |
michael@0 | 324 | NS_ASSERTION(NS_IsMainThread(), "Should only be called on main thread"); |
michael@0 | 325 | mResource = aResource; |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | // Return the principal of the current URI being played or downloaded. |
michael@0 | 329 | virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal(); |
michael@0 | 330 | |
michael@0 | 331 | // Return the time position in the video stream being |
michael@0 | 332 | // played measured in seconds. |
michael@0 | 333 | virtual double GetCurrentTime(); |
michael@0 | 334 | |
michael@0 | 335 | // Seek to the time position in (seconds) from the start of the video. |
michael@0 | 336 | // If aDoFastSeek is true, we'll seek to the sync point/keyframe preceeding |
michael@0 | 337 | // the seek target. |
michael@0 | 338 | virtual nsresult Seek(double aTime, SeekTarget::Type aSeekType); |
michael@0 | 339 | |
michael@0 | 340 | // Enables decoders to supply an enclosing byte range for a seek offset. |
michael@0 | 341 | // E.g. used by ChannelMediaResource to download a whole cluster for |
michael@0 | 342 | // DASH-WebM. |
michael@0 | 343 | virtual nsresult GetByteRangeForSeek(int64_t const aOffset, |
michael@0 | 344 | MediaByteRange &aByteRange) { |
michael@0 | 345 | return NS_ERROR_NOT_AVAILABLE; |
michael@0 | 346 | } |
michael@0 | 347 | |
michael@0 | 348 | // Initialize state machine and schedule it. |
michael@0 | 349 | nsresult InitializeStateMachine(MediaDecoder* aCloneDonor); |
michael@0 | 350 | |
michael@0 | 351 | // Start playback of a video. 'Load' must have previously been |
michael@0 | 352 | // called. |
michael@0 | 353 | virtual nsresult Play(); |
michael@0 | 354 | |
michael@0 | 355 | // Set/Unset dormant state if necessary. |
michael@0 | 356 | // Dormant state is a state to free all scarce media resources |
michael@0 | 357 | // (like hw video codec), did not decoding and stay dormant. |
michael@0 | 358 | // It is used to share scarece media resources in system. |
michael@0 | 359 | virtual void SetDormantIfNecessary(bool aDormant); |
michael@0 | 360 | |
michael@0 | 361 | // Pause video playback. |
michael@0 | 362 | virtual void Pause(); |
michael@0 | 363 | // Adjust the speed of the playback, optionally with pitch correction, |
michael@0 | 364 | virtual void SetVolume(double aVolume); |
michael@0 | 365 | // Sets whether audio is being captured. If it is, we won't play any |
michael@0 | 366 | // of our audio. |
michael@0 | 367 | virtual void SetAudioCaptured(bool aCaptured); |
michael@0 | 368 | |
michael@0 | 369 | virtual void NotifyWaitingForResourcesStatusChanged() MOZ_OVERRIDE; |
michael@0 | 370 | |
michael@0 | 371 | virtual void SetPlaybackRate(double aPlaybackRate); |
michael@0 | 372 | void SetPreservesPitch(bool aPreservesPitch); |
michael@0 | 373 | |
michael@0 | 374 | // Directs the decoder to not preroll extra samples until the media is |
michael@0 | 375 | // played. This reduces the memory overhead of media elements that may |
michael@0 | 376 | // not be played. Note that seeking also doesn't cause us start prerolling. |
michael@0 | 377 | void SetMinimizePrerollUntilPlaybackStarts(); |
michael@0 | 378 | |
michael@0 | 379 | // All MediaStream-related data is protected by mReentrantMonitor. |
michael@0 | 380 | // We have at most one DecodedStreamData per MediaDecoder. Its stream |
michael@0 | 381 | // is used as the input for each ProcessedMediaStream created by calls to |
michael@0 | 382 | // captureStream(UntilEnded). Seeking creates a new source stream, as does |
michael@0 | 383 | // replaying after the input as ended. In the latter case, the new source is |
michael@0 | 384 | // not connected to streams created by captureStreamUntilEnded. |
michael@0 | 385 | |
michael@0 | 386 | struct DecodedStreamData { |
michael@0 | 387 | typedef gfx::IntSize IntSize; |
michael@0 | 388 | |
michael@0 | 389 | DecodedStreamData(MediaDecoder* aDecoder, |
michael@0 | 390 | int64_t aInitialTime, SourceMediaStream* aStream); |
michael@0 | 391 | ~DecodedStreamData(); |
michael@0 | 392 | |
michael@0 | 393 | StreamTime GetLastOutputTime() { return mListener->GetLastOutputTime(); } |
michael@0 | 394 | bool IsFinished() { return mListener->IsFinishedOnMainThread(); } |
michael@0 | 395 | |
michael@0 | 396 | // The following group of fields are protected by the decoder's monitor |
michael@0 | 397 | // and can be read or written on any thread. |
michael@0 | 398 | int64_t mLastAudioPacketTime; // microseconds |
michael@0 | 399 | int64_t mLastAudioPacketEndTime; // microseconds |
michael@0 | 400 | // Count of audio frames written to the stream |
michael@0 | 401 | int64_t mAudioFramesWritten; |
michael@0 | 402 | // Saved value of aInitialTime. Timestamp of the first audio and/or |
michael@0 | 403 | // video packet written. |
michael@0 | 404 | int64_t mInitialTime; // microseconds |
michael@0 | 405 | // mNextVideoTime is the end timestamp for the last packet sent to the stream. |
michael@0 | 406 | // Therefore video packets starting at or after this time need to be copied |
michael@0 | 407 | // to the output stream. |
michael@0 | 408 | int64_t mNextVideoTime; // microseconds |
michael@0 | 409 | MediaDecoder* mDecoder; |
michael@0 | 410 | // The last video image sent to the stream. Useful if we need to replicate |
michael@0 | 411 | // the image. |
michael@0 | 412 | nsRefPtr<layers::Image> mLastVideoImage; |
michael@0 | 413 | IntSize mLastVideoImageDisplaySize; |
michael@0 | 414 | // This is set to true when the stream is initialized (audio and |
michael@0 | 415 | // video tracks added). |
michael@0 | 416 | bool mStreamInitialized; |
michael@0 | 417 | bool mHaveSentFinish; |
michael@0 | 418 | bool mHaveSentFinishAudio; |
michael@0 | 419 | bool mHaveSentFinishVideo; |
michael@0 | 420 | |
michael@0 | 421 | // The decoder is responsible for calling Destroy() on this stream. |
michael@0 | 422 | // Can be read from any thread. |
michael@0 | 423 | const nsRefPtr<SourceMediaStream> mStream; |
michael@0 | 424 | // Can be read from any thread. |
michael@0 | 425 | nsRefPtr<DecodedStreamGraphListener> mListener; |
michael@0 | 426 | // True when we've explicitly blocked this stream because we're |
michael@0 | 427 | // not in PLAY_STATE_PLAYING. Used on the main thread only. |
michael@0 | 428 | bool mHaveBlockedForPlayState; |
michael@0 | 429 | // We also have an explicit blocker on the stream when |
michael@0 | 430 | // mDecoderStateMachine is non-null and MediaDecoderStateMachine is false. |
michael@0 | 431 | bool mHaveBlockedForStateMachineNotPlaying; |
michael@0 | 432 | }; |
michael@0 | 433 | |
michael@0 | 434 | class DecodedStreamGraphListener : public MediaStreamListener { |
michael@0 | 435 | public: |
michael@0 | 436 | DecodedStreamGraphListener(MediaStream* aStream, DecodedStreamData* aData); |
michael@0 | 437 | virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) MOZ_OVERRIDE; |
michael@0 | 438 | virtual void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE; |
michael@0 | 439 | |
michael@0 | 440 | void DoNotifyFinished(); |
michael@0 | 441 | |
michael@0 | 442 | StreamTime GetLastOutputTime() |
michael@0 | 443 | { |
michael@0 | 444 | MutexAutoLock lock(mMutex); |
michael@0 | 445 | return mLastOutputTime; |
michael@0 | 446 | } |
michael@0 | 447 | void Forget() |
michael@0 | 448 | { |
michael@0 | 449 | NS_ASSERTION(NS_IsMainThread(), "Main thread only"); |
michael@0 | 450 | mData = nullptr; |
michael@0 | 451 | |
michael@0 | 452 | MutexAutoLock lock(mMutex); |
michael@0 | 453 | mStream = nullptr; |
michael@0 | 454 | } |
michael@0 | 455 | bool SetFinishedOnMainThread(bool aFinished) |
michael@0 | 456 | { |
michael@0 | 457 | MutexAutoLock lock(mMutex); |
michael@0 | 458 | bool result = !mStreamFinishedOnMainThread; |
michael@0 | 459 | mStreamFinishedOnMainThread = aFinished; |
michael@0 | 460 | return result; |
michael@0 | 461 | } |
michael@0 | 462 | bool IsFinishedOnMainThread() |
michael@0 | 463 | { |
michael@0 | 464 | MutexAutoLock lock(mMutex); |
michael@0 | 465 | return mStreamFinishedOnMainThread; |
michael@0 | 466 | } |
michael@0 | 467 | private: |
michael@0 | 468 | // Main thread only |
michael@0 | 469 | DecodedStreamData* mData; |
michael@0 | 470 | |
michael@0 | 471 | Mutex mMutex; |
michael@0 | 472 | // Protected by mMutex |
michael@0 | 473 | nsRefPtr<MediaStream> mStream; |
michael@0 | 474 | // Protected by mMutex |
michael@0 | 475 | StreamTime mLastOutputTime; |
michael@0 | 476 | // Protected by mMutex |
michael@0 | 477 | bool mStreamFinishedOnMainThread; |
michael@0 | 478 | }; |
michael@0 | 479 | |
michael@0 | 480 | struct OutputStreamData { |
michael@0 | 481 | void Init(ProcessedMediaStream* aStream, bool aFinishWhenEnded) |
michael@0 | 482 | { |
michael@0 | 483 | mStream = aStream; |
michael@0 | 484 | mFinishWhenEnded = aFinishWhenEnded; |
michael@0 | 485 | } |
michael@0 | 486 | nsRefPtr<ProcessedMediaStream> mStream; |
michael@0 | 487 | // mPort connects mDecodedStream->mStream to our mStream. |
michael@0 | 488 | nsRefPtr<MediaInputPort> mPort; |
michael@0 | 489 | bool mFinishWhenEnded; |
michael@0 | 490 | }; |
michael@0 | 491 | /** |
michael@0 | 492 | * Connects mDecodedStream->mStream to aStream->mStream. |
michael@0 | 493 | */ |
michael@0 | 494 | void ConnectDecodedStreamToOutputStream(OutputStreamData* aStream); |
michael@0 | 495 | /** |
michael@0 | 496 | * Disconnects mDecodedStream->mStream from all outputs and clears |
michael@0 | 497 | * mDecodedStream. |
michael@0 | 498 | */ |
michael@0 | 499 | void DestroyDecodedStream(); |
michael@0 | 500 | /** |
michael@0 | 501 | * Recreates mDecodedStream. Call this to create mDecodedStream at first, |
michael@0 | 502 | * and when seeking, to ensure a new stream is set up with fresh buffers. |
michael@0 | 503 | * aStartTimeUSecs is relative to the state machine's mStartTime. |
michael@0 | 504 | * Decoder monitor must be held. |
michael@0 | 505 | */ |
michael@0 | 506 | void RecreateDecodedStream(int64_t aStartTimeUSecs); |
michael@0 | 507 | /** |
michael@0 | 508 | * Call this when mDecoderStateMachine or mDecoderStateMachine->IsPlaying() changes. |
michael@0 | 509 | * Decoder monitor must be held. |
michael@0 | 510 | */ |
michael@0 | 511 | void UpdateStreamBlockingForStateMachinePlaying(); |
michael@0 | 512 | nsTArray<OutputStreamData>& OutputStreams() |
michael@0 | 513 | { |
michael@0 | 514 | GetReentrantMonitor().AssertCurrentThreadIn(); |
michael@0 | 515 | return mOutputStreams; |
michael@0 | 516 | } |
michael@0 | 517 | DecodedStreamData* GetDecodedStream() |
michael@0 | 518 | { |
michael@0 | 519 | GetReentrantMonitor().AssertCurrentThreadIn(); |
michael@0 | 520 | return mDecodedStream; |
michael@0 | 521 | } |
michael@0 | 522 | |
michael@0 | 523 | // Add an output stream. All decoder output will be sent to the stream. |
michael@0 | 524 | // The stream is initially blocked. The decoder is responsible for unblocking |
michael@0 | 525 | // it while it is playing back. |
michael@0 | 526 | virtual void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded); |
michael@0 | 527 | |
michael@0 | 528 | // Return the duration of the video in seconds. |
michael@0 | 529 | virtual double GetDuration(); |
michael@0 | 530 | |
michael@0 | 531 | // Return the duration of the video in seconds. |
michael@0 | 532 | int64_t GetMediaDuration() MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 533 | |
michael@0 | 534 | // A media stream is assumed to be infinite if the metadata doesn't |
michael@0 | 535 | // contain the duration, and range requests are not supported, and |
michael@0 | 536 | // no headers give a hint of a possible duration (Content-Length, |
michael@0 | 537 | // Content-Duration, and variants), and we cannot seek in the media |
michael@0 | 538 | // stream to determine the duration. |
michael@0 | 539 | // |
michael@0 | 540 | // When the media stream ends, we can know the duration, thus the stream is |
michael@0 | 541 | // no longer considered to be infinite. |
michael@0 | 542 | virtual void SetInfinite(bool aInfinite); |
michael@0 | 543 | |
michael@0 | 544 | // Return true if the stream is infinite (see SetInfinite). |
michael@0 | 545 | virtual bool IsInfinite(); |
michael@0 | 546 | |
michael@0 | 547 | // Called by MediaResource when the "cache suspended" status changes. |
michael@0 | 548 | // If MediaResource::IsSuspendedByCache returns true, then the decoder |
michael@0 | 549 | // should stop buffering or otherwise waiting for download progress and |
michael@0 | 550 | // start consuming data, if possible, because the cache is full. |
michael@0 | 551 | virtual void NotifySuspendedStatusChanged(); |
michael@0 | 552 | |
michael@0 | 553 | // Called by MediaResource when some data has been received. |
michael@0 | 554 | // Call on the main thread only. |
michael@0 | 555 | virtual void NotifyBytesDownloaded(); |
michael@0 | 556 | |
michael@0 | 557 | // Called by nsChannelToPipeListener or MediaResource when the |
michael@0 | 558 | // download has ended. Called on the main thread only. aStatus is |
michael@0 | 559 | // the result from OnStopRequest. |
michael@0 | 560 | virtual void NotifyDownloadEnded(nsresult aStatus); |
michael@0 | 561 | |
michael@0 | 562 | // Called as data arrives on the stream and is read into the cache. Called |
michael@0 | 563 | // on the main thread only. |
michael@0 | 564 | virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset); |
michael@0 | 565 | |
michael@0 | 566 | // Called by MediaResource when the principal of the resource has |
michael@0 | 567 | // changed. Called on main thread only. |
michael@0 | 568 | virtual void NotifyPrincipalChanged(); |
michael@0 | 569 | |
michael@0 | 570 | // Called by the MediaResource to keep track of the number of bytes read |
michael@0 | 571 | // from the resource. Called on the main by an event runner dispatched |
michael@0 | 572 | // by the MediaResource read functions. |
michael@0 | 573 | void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 574 | |
michael@0 | 575 | int64_t GetEndMediaTime() const MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 576 | |
michael@0 | 577 | // Return true if we are currently seeking in the media resource. |
michael@0 | 578 | // Call on the main thread only. |
michael@0 | 579 | virtual bool IsSeeking() const; |
michael@0 | 580 | |
michael@0 | 581 | // Return true if the decoder has reached the end of playback. |
michael@0 | 582 | // Call on the main thread only. |
michael@0 | 583 | virtual bool IsEnded() const; |
michael@0 | 584 | |
michael@0 | 585 | // Set the duration of the media resource in units of seconds. |
michael@0 | 586 | // This is called via a channel listener if it can pick up the duration |
michael@0 | 587 | // from a content header. Must be called from the main thread only. |
michael@0 | 588 | virtual void SetDuration(double aDuration); |
michael@0 | 589 | |
michael@0 | 590 | // Sets the initial duration of the media. Called while the media metadata |
michael@0 | 591 | // is being read and the decode is being setup. |
michael@0 | 592 | void SetMediaDuration(int64_t aDuration) MOZ_OVERRIDE; |
michael@0 | 593 | // Updates the media duration. This is called while the media is being |
michael@0 | 594 | // played, calls before the media has reached loaded metadata are ignored. |
michael@0 | 595 | // The duration is assumed to be an estimate, and so a degree of |
michael@0 | 596 | // instability is expected; if the incoming duration is not significantly |
michael@0 | 597 | // different from the existing duration, the change request is ignored. |
michael@0 | 598 | // If the incoming duration is significantly different, the duration is |
michael@0 | 599 | // changed, this causes a durationchanged event to fire to the media |
michael@0 | 600 | // element. |
michael@0 | 601 | void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE; |
michael@0 | 602 | |
michael@0 | 603 | // Set a flag indicating whether seeking is supported |
michael@0 | 604 | virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE; |
michael@0 | 605 | virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 606 | // Returns true if this media supports seeking. False for example for WebM |
michael@0 | 607 | // files without an index and chained ogg files. |
michael@0 | 608 | virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 609 | // Returns true if seeking is supported on a transport level (e.g. the server |
michael@0 | 610 | // supports range requests, we are playing a file, etc.). |
michael@0 | 611 | virtual bool IsTransportSeekable(); |
michael@0 | 612 | |
michael@0 | 613 | // Return the time ranges that can be seeked into. |
michael@0 | 614 | virtual nsresult GetSeekable(dom::TimeRanges* aSeekable); |
michael@0 | 615 | |
michael@0 | 616 | // Set the end time of the media resource. When playback reaches |
michael@0 | 617 | // this point the media pauses. aTime is in seconds. |
michael@0 | 618 | virtual void SetFragmentEndTime(double aTime); |
michael@0 | 619 | |
michael@0 | 620 | // Set the end time of the media. aTime is in microseconds. |
michael@0 | 621 | void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 622 | |
michael@0 | 623 | // Invalidate the frame. |
michael@0 | 624 | void Invalidate(); |
michael@0 | 625 | void InvalidateWithFlags(uint32_t aFlags); |
michael@0 | 626 | |
michael@0 | 627 | // Suspend any media downloads that are in progress. Called by the |
michael@0 | 628 | // media element when it is sent to the bfcache, or when we need |
michael@0 | 629 | // to throttle the download. Call on the main thread only. This can |
michael@0 | 630 | // be called multiple times, there's an internal "suspend count". |
michael@0 | 631 | virtual void Suspend(); |
michael@0 | 632 | |
michael@0 | 633 | // Resume any media downloads that have been suspended. Called by the |
michael@0 | 634 | // media element when it is restored from the bfcache, or when we need |
michael@0 | 635 | // to stop throttling the download. Call on the main thread only. |
michael@0 | 636 | // The download will only actually resume once as many Resume calls |
michael@0 | 637 | // have been made as Suspend calls. When aForceBuffering is true, |
michael@0 | 638 | // we force the decoder to go into buffering state before resuming |
michael@0 | 639 | // playback. |
michael@0 | 640 | virtual void Resume(bool aForceBuffering); |
michael@0 | 641 | |
michael@0 | 642 | // Moves any existing channel loads into the background, so that they don't |
michael@0 | 643 | // block the load event. This is called when we stop delaying the load |
michael@0 | 644 | // event. Any new loads initiated (for example to seek) will also be in the |
michael@0 | 645 | // background. Implementations of this must call MoveLoadsToBackground() on |
michael@0 | 646 | // their MediaResource. |
michael@0 | 647 | virtual void MoveLoadsToBackground(); |
michael@0 | 648 | |
michael@0 | 649 | // Returns a weak reference to the media decoder owner. |
michael@0 | 650 | MediaDecoderOwner* GetMediaOwner() const; |
michael@0 | 651 | |
michael@0 | 652 | // Called by the state machine to notify the decoder that the duration |
michael@0 | 653 | // has changed. |
michael@0 | 654 | void DurationChanged(); |
michael@0 | 655 | |
michael@0 | 656 | bool OnStateMachineThread() const MOZ_OVERRIDE; |
michael@0 | 657 | |
michael@0 | 658 | bool OnDecodeThread() const MOZ_OVERRIDE; |
michael@0 | 659 | |
michael@0 | 660 | // Returns the monitor for other threads to synchronise access to |
michael@0 | 661 | // state. |
michael@0 | 662 | ReentrantMonitor& GetReentrantMonitor() MOZ_OVERRIDE; |
michael@0 | 663 | |
michael@0 | 664 | // Returns true if the decoder is shut down |
michael@0 | 665 | bool IsShutdown() const MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 666 | |
michael@0 | 667 | // Constructs the time ranges representing what segments of the media |
michael@0 | 668 | // are buffered and playable. |
michael@0 | 669 | virtual nsresult GetBuffered(dom::TimeRanges* aBuffered); |
michael@0 | 670 | |
michael@0 | 671 | // Returns the size, in bytes, of the heap memory used by the currently |
michael@0 | 672 | // queued decoded video and audio data. |
michael@0 | 673 | size_t SizeOfVideoQueue(); |
michael@0 | 674 | size_t SizeOfAudioQueue(); |
michael@0 | 675 | |
michael@0 | 676 | VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE |
michael@0 | 677 | { |
michael@0 | 678 | return mVideoFrameContainer; |
michael@0 | 679 | } |
michael@0 | 680 | layers::ImageContainer* GetImageContainer() MOZ_OVERRIDE; |
michael@0 | 681 | |
michael@0 | 682 | // Return the current state. Can be called on any thread. If called from |
michael@0 | 683 | // a non-main thread, the decoder monitor must be held. |
michael@0 | 684 | PlayState GetState() { |
michael@0 | 685 | return mPlayState; |
michael@0 | 686 | } |
michael@0 | 687 | |
michael@0 | 688 | // Fire progress events if needed according to the time and byte |
michael@0 | 689 | // constraints outlined in the specification. aTimer is true |
michael@0 | 690 | // if the method is called as a result of the progress timer rather |
michael@0 | 691 | // than the result of downloaded data. |
michael@0 | 692 | void Progress(bool aTimer); |
michael@0 | 693 | |
michael@0 | 694 | // Fire timeupdate events if needed according to the time constraints |
michael@0 | 695 | // outlined in the specification. |
michael@0 | 696 | void FireTimeUpdate(); |
michael@0 | 697 | |
michael@0 | 698 | // Stop updating the bytes downloaded for progress notifications. Called |
michael@0 | 699 | // when seeking to prevent wild changes to the progress notification. |
michael@0 | 700 | // Must be called with the decoder monitor held. |
michael@0 | 701 | virtual void StopProgressUpdates(); |
michael@0 | 702 | |
michael@0 | 703 | // Allow updating the bytes downloaded for progress notifications. Must |
michael@0 | 704 | // be called with the decoder monitor held. |
michael@0 | 705 | virtual void StartProgressUpdates(); |
michael@0 | 706 | |
michael@0 | 707 | // Something has changed that could affect the computed playback rate, |
michael@0 | 708 | // so recompute it. The monitor must be held. |
michael@0 | 709 | virtual void UpdatePlaybackRate(); |
michael@0 | 710 | |
michael@0 | 711 | // Used to estimate rates of data passing through the decoder's channel. |
michael@0 | 712 | // Records activity stopping on the channel. The monitor must be held. |
michael@0 | 713 | virtual void NotifyPlaybackStarted() { |
michael@0 | 714 | GetReentrantMonitor().AssertCurrentThreadIn(); |
michael@0 | 715 | mPlaybackStatistics.Start(); |
michael@0 | 716 | } |
michael@0 | 717 | |
michael@0 | 718 | // Used to estimate rates of data passing through the decoder's channel. |
michael@0 | 719 | // Records activity stopping on the channel. The monitor must be held. |
michael@0 | 720 | virtual void NotifyPlaybackStopped() { |
michael@0 | 721 | GetReentrantMonitor().AssertCurrentThreadIn(); |
michael@0 | 722 | mPlaybackStatistics.Stop(); |
michael@0 | 723 | } |
michael@0 | 724 | |
michael@0 | 725 | // The actual playback rate computation. The monitor must be held. |
michael@0 | 726 | virtual double ComputePlaybackRate(bool* aReliable); |
michael@0 | 727 | |
michael@0 | 728 | // Return true when the media is same-origin with the element. The monitor |
michael@0 | 729 | // must be held. |
michael@0 | 730 | bool IsSameOriginMedia(); |
michael@0 | 731 | |
michael@0 | 732 | // Returns true if we can play the entire media through without stopping |
michael@0 | 733 | // to buffer, given the current download and playback rates. |
michael@0 | 734 | bool CanPlayThrough(); |
michael@0 | 735 | |
michael@0 | 736 | // Make the decoder state machine update the playback position. Called by |
michael@0 | 737 | // the reader on the decoder thread (Assertions for this checked by |
michael@0 | 738 | // mDecoderStateMachine). This must be called with the decode monitor |
michael@0 | 739 | // held. |
michael@0 | 740 | void UpdatePlaybackPosition(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE; |
michael@0 | 741 | |
michael@0 | 742 | void SetAudioChannel(dom::AudioChannel aChannel) { mAudioChannel = aChannel; } |
michael@0 | 743 | dom::AudioChannel GetAudioChannel() { return mAudioChannel; } |
michael@0 | 744 | |
michael@0 | 745 | // Send a new set of metadata to the state machine, to be dispatched to the |
michael@0 | 746 | // main thread to be presented when the |currentTime| of the media is greater |
michael@0 | 747 | // or equal to aPublishTime. |
michael@0 | 748 | void QueueMetadata(int64_t aPublishTime, |
michael@0 | 749 | int aChannels, |
michael@0 | 750 | int aRate, |
michael@0 | 751 | bool aHasAudio, |
michael@0 | 752 | bool aHasVideo, |
michael@0 | 753 | MetadataTags* aTags); |
michael@0 | 754 | |
michael@0 | 755 | /****** |
michael@0 | 756 | * The following methods must only be called on the main |
michael@0 | 757 | * thread. |
michael@0 | 758 | ******/ |
michael@0 | 759 | |
michael@0 | 760 | // Change to a new play state. This updates the mState variable and |
michael@0 | 761 | // notifies any thread blocking on this object's monitor of the |
michael@0 | 762 | // change. Call on the main thread only. |
michael@0 | 763 | virtual void ChangeState(PlayState aState); |
michael@0 | 764 | |
michael@0 | 765 | // Called by |ChangeState|, to update the state machine. |
michael@0 | 766 | // Call on the main thread only and the lock must be obtained. |
michael@0 | 767 | virtual void ApplyStateToStateMachine(PlayState aState); |
michael@0 | 768 | |
michael@0 | 769 | // May be called by the reader to notify this decoder that the metadata from |
michael@0 | 770 | // the media file has been read. Call on the decode thread only. |
michael@0 | 771 | void OnReadMetadataCompleted() MOZ_OVERRIDE { } |
michael@0 | 772 | |
michael@0 | 773 | // Called when the metadata from the media file has been loaded by the |
michael@0 | 774 | // state machine. Call on the main thread only. |
michael@0 | 775 | virtual void MetadataLoaded(int aChannels, |
michael@0 | 776 | int aRate, |
michael@0 | 777 | bool aHasAudio, |
michael@0 | 778 | bool aHasVideo, |
michael@0 | 779 | MetadataTags* aTags); |
michael@0 | 780 | |
michael@0 | 781 | // Called when the first frame has been loaded. |
michael@0 | 782 | // Call on the main thread only. |
michael@0 | 783 | void FirstFrameLoaded(); |
michael@0 | 784 | |
michael@0 | 785 | // Returns true if the resource has been loaded. Acquires the monitor. |
michael@0 | 786 | // Call from any thread. |
michael@0 | 787 | virtual bool IsDataCachedToEndOfResource(); |
michael@0 | 788 | |
michael@0 | 789 | // Called when the video has completed playing. |
michael@0 | 790 | // Call on the main thread only. |
michael@0 | 791 | void PlaybackEnded(); |
michael@0 | 792 | |
michael@0 | 793 | // Seeking has stopped. Inform the element on the main |
michael@0 | 794 | // thread. |
michael@0 | 795 | void SeekingStopped(); |
michael@0 | 796 | |
michael@0 | 797 | // Seeking has stopped at the end of the resource. Inform the element on the main |
michael@0 | 798 | // thread. |
michael@0 | 799 | void SeekingStoppedAtEnd(); |
michael@0 | 800 | |
michael@0 | 801 | // Seeking has started. Inform the element on the main |
michael@0 | 802 | // thread. |
michael@0 | 803 | void SeekingStarted(); |
michael@0 | 804 | |
michael@0 | 805 | // Called when the backend has changed the current playback |
michael@0 | 806 | // position. It dispatches a timeupdate event and invalidates the frame. |
michael@0 | 807 | // This must be called on the main thread only. |
michael@0 | 808 | void PlaybackPositionChanged(); |
michael@0 | 809 | |
michael@0 | 810 | // Calls mElement->UpdateReadyStateForData, telling it whether we have |
michael@0 | 811 | // data for the next frame and if we're buffering. Main thread only. |
michael@0 | 812 | virtual void UpdateReadyStateForData(); |
michael@0 | 813 | |
michael@0 | 814 | // Find the end of the cached data starting at the current decoder |
michael@0 | 815 | // position. |
michael@0 | 816 | int64_t GetDownloadPosition(); |
michael@0 | 817 | |
michael@0 | 818 | // Updates the approximate byte offset which playback has reached. This is |
michael@0 | 819 | // used to calculate the readyState transitions. |
michael@0 | 820 | void UpdatePlaybackOffset(int64_t aOffset); |
michael@0 | 821 | |
michael@0 | 822 | // Provide access to the state machine object |
michael@0 | 823 | MediaDecoderStateMachine* GetStateMachine() const; |
michael@0 | 824 | |
michael@0 | 825 | // Drop reference to state machine. Only called during shutdown dance. |
michael@0 | 826 | virtual void ReleaseStateMachine(); |
michael@0 | 827 | |
michael@0 | 828 | // Notifies the element that decoding has failed. |
michael@0 | 829 | virtual void DecodeError(); |
michael@0 | 830 | |
michael@0 | 831 | // Indicate whether the media is same-origin with the element. |
michael@0 | 832 | void UpdateSameOriginStatus(bool aSameOrigin); |
michael@0 | 833 | |
michael@0 | 834 | MediaDecoderOwner* GetOwner() MOZ_OVERRIDE; |
michael@0 | 835 | |
michael@0 | 836 | // Returns true if we're logically playing, that is, if the Play() has |
michael@0 | 837 | // been called and Pause() has not or we have not yet reached the end |
michael@0 | 838 | // of media. This is irrespective of the seeking state; if the owner |
michael@0 | 839 | // calls Play() and then Seek(), we still count as logically playing. |
michael@0 | 840 | // The decoder monitor must be held. |
michael@0 | 841 | bool IsLogicallyPlaying(); |
michael@0 | 842 | |
michael@0 | 843 | #ifdef MOZ_RAW |
michael@0 | 844 | static bool IsRawEnabled(); |
michael@0 | 845 | #endif |
michael@0 | 846 | |
michael@0 | 847 | static bool IsOggEnabled(); |
michael@0 | 848 | static bool IsOpusEnabled(); |
michael@0 | 849 | |
michael@0 | 850 | #ifdef MOZ_WAVE |
michael@0 | 851 | static bool IsWaveEnabled(); |
michael@0 | 852 | #endif |
michael@0 | 853 | |
michael@0 | 854 | #ifdef MOZ_WEBM |
michael@0 | 855 | static bool IsWebMEnabled(); |
michael@0 | 856 | #endif |
michael@0 | 857 | #ifdef NECKO_PROTOCOL_rtsp |
michael@0 | 858 | static bool IsRtspEnabled(); |
michael@0 | 859 | #endif |
michael@0 | 860 | |
michael@0 | 861 | #ifdef MOZ_GSTREAMER |
michael@0 | 862 | static bool IsGStreamerEnabled(); |
michael@0 | 863 | #endif |
michael@0 | 864 | |
michael@0 | 865 | #ifdef MOZ_OMX_DECODER |
michael@0 | 866 | static bool IsOmxEnabled(); |
michael@0 | 867 | #endif |
michael@0 | 868 | |
michael@0 | 869 | #ifdef MOZ_MEDIA_PLUGINS |
michael@0 | 870 | static bool IsMediaPluginsEnabled(); |
michael@0 | 871 | #endif |
michael@0 | 872 | |
michael@0 | 873 | #ifdef MOZ_WMF |
michael@0 | 874 | static bool IsWMFEnabled(); |
michael@0 | 875 | #endif |
michael@0 | 876 | |
michael@0 | 877 | #ifdef MOZ_APPLEMEDIA |
michael@0 | 878 | static bool IsAppleMP3Enabled(); |
michael@0 | 879 | #endif |
michael@0 | 880 | |
michael@0 | 881 | // Schedules the state machine to run one cycle on the shared state |
michael@0 | 882 | // machine thread. Main thread only. |
michael@0 | 883 | nsresult ScheduleStateMachineThread(); |
michael@0 | 884 | |
michael@0 | 885 | struct Statistics { |
michael@0 | 886 | // Estimate of the current playback rate (bytes/second). |
michael@0 | 887 | double mPlaybackRate; |
michael@0 | 888 | // Estimate of the current download rate (bytes/second). This |
michael@0 | 889 | // ignores time that the channel was paused by Gecko. |
michael@0 | 890 | double mDownloadRate; |
michael@0 | 891 | // Total length of media stream in bytes; -1 if not known |
michael@0 | 892 | int64_t mTotalBytes; |
michael@0 | 893 | // Current position of the download, in bytes. This is the offset of |
michael@0 | 894 | // the first uncached byte after the decoder position. |
michael@0 | 895 | int64_t mDownloadPosition; |
michael@0 | 896 | // Current position of decoding, in bytes (how much of the stream |
michael@0 | 897 | // has been consumed) |
michael@0 | 898 | int64_t mDecoderPosition; |
michael@0 | 899 | // Current position of playback, in bytes |
michael@0 | 900 | int64_t mPlaybackPosition; |
michael@0 | 901 | // If false, then mDownloadRate cannot be considered a reliable |
michael@0 | 902 | // estimate (probably because the download has only been running |
michael@0 | 903 | // a short time). |
michael@0 | 904 | bool mDownloadRateReliable; |
michael@0 | 905 | // If false, then mPlaybackRate cannot be considered a reliable |
michael@0 | 906 | // estimate (probably because playback has only been running |
michael@0 | 907 | // a short time). |
michael@0 | 908 | bool mPlaybackRateReliable; |
michael@0 | 909 | }; |
michael@0 | 910 | |
michael@0 | 911 | // Return statistics. This is used for progress events and other things. |
michael@0 | 912 | // This can be called from any thread. It's only a snapshot of the |
michael@0 | 913 | // current state, since other threads might be changing the state |
michael@0 | 914 | // at any time. |
michael@0 | 915 | virtual Statistics GetStatistics(); |
michael@0 | 916 | |
michael@0 | 917 | // Frame decoding/painting related performance counters. |
michael@0 | 918 | // Threadsafe. |
michael@0 | 919 | class FrameStatistics { |
michael@0 | 920 | public: |
michael@0 | 921 | |
michael@0 | 922 | FrameStatistics() : |
michael@0 | 923 | mReentrantMonitor("MediaDecoder::FrameStats"), |
michael@0 | 924 | mParsedFrames(0), |
michael@0 | 925 | mDecodedFrames(0), |
michael@0 | 926 | mPresentedFrames(0) {} |
michael@0 | 927 | |
michael@0 | 928 | // Returns number of frames which have been parsed from the media. |
michael@0 | 929 | // Can be called on any thread. |
michael@0 | 930 | uint32_t GetParsedFrames() { |
michael@0 | 931 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 932 | return mParsedFrames; |
michael@0 | 933 | } |
michael@0 | 934 | |
michael@0 | 935 | // Returns the number of parsed frames which have been decoded. |
michael@0 | 936 | // Can be called on any thread. |
michael@0 | 937 | uint32_t GetDecodedFrames() { |
michael@0 | 938 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 939 | return mDecodedFrames; |
michael@0 | 940 | } |
michael@0 | 941 | |
michael@0 | 942 | // Returns the number of decoded frames which have been sent to the rendering |
michael@0 | 943 | // pipeline for painting ("presented"). |
michael@0 | 944 | // Can be called on any thread. |
michael@0 | 945 | uint32_t GetPresentedFrames() { |
michael@0 | 946 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 947 | return mPresentedFrames; |
michael@0 | 948 | } |
michael@0 | 949 | |
michael@0 | 950 | // Increments the parsed and decoded frame counters by the passed in counts. |
michael@0 | 951 | // Can be called on any thread. |
michael@0 | 952 | void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) { |
michael@0 | 953 | if (aParsed == 0 && aDecoded == 0) |
michael@0 | 954 | return; |
michael@0 | 955 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 956 | mParsedFrames += aParsed; |
michael@0 | 957 | mDecodedFrames += aDecoded; |
michael@0 | 958 | } |
michael@0 | 959 | |
michael@0 | 960 | // Increments the presented frame counters. |
michael@0 | 961 | // Can be called on any thread. |
michael@0 | 962 | void NotifyPresentedFrame() { |
michael@0 | 963 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 964 | ++mPresentedFrames; |
michael@0 | 965 | } |
michael@0 | 966 | |
michael@0 | 967 | private: |
michael@0 | 968 | |
michael@0 | 969 | // ReentrantMonitor to protect access of playback statistics. |
michael@0 | 970 | ReentrantMonitor mReentrantMonitor; |
michael@0 | 971 | |
michael@0 | 972 | // Number of frames parsed and demuxed from media. |
michael@0 | 973 | // Access protected by mReentrantMonitor. |
michael@0 | 974 | uint32_t mParsedFrames; |
michael@0 | 975 | |
michael@0 | 976 | // Number of parsed frames which were actually decoded. |
michael@0 | 977 | // Access protected by mReentrantMonitor. |
michael@0 | 978 | uint32_t mDecodedFrames; |
michael@0 | 979 | |
michael@0 | 980 | // Number of decoded frames which were actually sent down the rendering |
michael@0 | 981 | // pipeline to be painted ("presented"). Access protected by mReentrantMonitor. |
michael@0 | 982 | uint32_t mPresentedFrames; |
michael@0 | 983 | }; |
michael@0 | 984 | |
michael@0 | 985 | // Return the frame decode/paint related statistics. |
michael@0 | 986 | FrameStatistics& GetFrameStatistics() { return mFrameStats; } |
michael@0 | 987 | |
michael@0 | 988 | // Increments the parsed and decoded frame counters by the passed in counts. |
michael@0 | 989 | // Can be called on any thread. |
michael@0 | 990 | virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE |
michael@0 | 991 | { |
michael@0 | 992 | GetFrameStatistics().NotifyDecodedFrames(aParsed, aDecoded); |
michael@0 | 993 | } |
michael@0 | 994 | |
michael@0 | 995 | protected: |
michael@0 | 996 | /****** |
michael@0 | 997 | * The following members should be accessed with the decoder lock held. |
michael@0 | 998 | ******/ |
michael@0 | 999 | |
michael@0 | 1000 | // Current decoding position in the stream. This is where the decoder |
michael@0 | 1001 | // is up to consuming the stream. This is not adjusted during decoder |
michael@0 | 1002 | // seek operations, but it's updated at the end when we start playing |
michael@0 | 1003 | // back again. |
michael@0 | 1004 | int64_t mDecoderPosition; |
michael@0 | 1005 | // Current playback position in the stream. This is (approximately) |
michael@0 | 1006 | // where we're up to playing back the stream. This is not adjusted |
michael@0 | 1007 | // during decoder seek operations, but it's updated at the end when we |
michael@0 | 1008 | // start playing back again. |
michael@0 | 1009 | int64_t mPlaybackPosition; |
michael@0 | 1010 | |
michael@0 | 1011 | // The current playback position of the media resource in units of |
michael@0 | 1012 | // seconds. This is updated approximately at the framerate of the |
michael@0 | 1013 | // video (if it is a video) or the callback period of the audio. |
michael@0 | 1014 | // It is read and written from the main thread only. |
michael@0 | 1015 | double mCurrentTime; |
michael@0 | 1016 | |
michael@0 | 1017 | // Volume that playback should start at. 0.0 = muted. 1.0 = full |
michael@0 | 1018 | // volume. Readable/Writeable from the main thread. |
michael@0 | 1019 | double mInitialVolume; |
michael@0 | 1020 | |
michael@0 | 1021 | // PlaybackRate and pitch preservation status we should start at. |
michael@0 | 1022 | // Readable/Writeable from the main thread. |
michael@0 | 1023 | double mInitialPlaybackRate; |
michael@0 | 1024 | bool mInitialPreservesPitch; |
michael@0 | 1025 | |
michael@0 | 1026 | // Duration of the media resource. Set to -1 if unknown. |
michael@0 | 1027 | // Set when the metadata is loaded. Accessed on the main thread |
michael@0 | 1028 | // only. |
michael@0 | 1029 | int64_t mDuration; |
michael@0 | 1030 | |
michael@0 | 1031 | // True when playback should start with audio captured (not playing). |
michael@0 | 1032 | bool mInitialAudioCaptured; |
michael@0 | 1033 | |
michael@0 | 1034 | // True if the resource is seekable at a transport level (server supports byte |
michael@0 | 1035 | // range requests, local file, etc.). |
michael@0 | 1036 | bool mTransportSeekable; |
michael@0 | 1037 | |
michael@0 | 1038 | // True if the media is seekable (i.e. supports random access). |
michael@0 | 1039 | bool mMediaSeekable; |
michael@0 | 1040 | |
michael@0 | 1041 | // True if the media is same-origin with the element. Data can only be |
michael@0 | 1042 | // passed to MediaStreams when this is true. |
michael@0 | 1043 | bool mSameOriginMedia; |
michael@0 | 1044 | |
michael@0 | 1045 | /****** |
michael@0 | 1046 | * The following member variables can be accessed from any thread. |
michael@0 | 1047 | ******/ |
michael@0 | 1048 | |
michael@0 | 1049 | // The state machine object for handling the decoding. It is safe to |
michael@0 | 1050 | // call methods of this object from other threads. Its internal data |
michael@0 | 1051 | // is synchronised on a monitor. The lifetime of this object is |
michael@0 | 1052 | // after mPlayState is LOADING and before mPlayState is SHUTDOWN. It |
michael@0 | 1053 | // is safe to access it during this period. |
michael@0 | 1054 | nsRefPtr<MediaDecoderStateMachine> mDecoderStateMachine; |
michael@0 | 1055 | |
michael@0 | 1056 | // Media data resource. |
michael@0 | 1057 | nsRefPtr<MediaResource> mResource; |
michael@0 | 1058 | |
michael@0 | 1059 | // |ReentrantMonitor| for detecting when the video play state changes. A call |
michael@0 | 1060 | // to |Wait| on this monitor will block the thread until the next state |
michael@0 | 1061 | // change. |
michael@0 | 1062 | // Using a wrapper class to restrict direct access to the |ReentrantMonitor| |
michael@0 | 1063 | // object. Subclasses may override |MediaDecoder|::|GetReentrantMonitor| |
michael@0 | 1064 | // e.g. |DASHRepDecoder|::|GetReentrantMonitor| returns the monitor in the |
michael@0 | 1065 | // main |DASHDecoder| object. In this case, directly accessing the |
michael@0 | 1066 | // member variable mReentrantMonitor in |DASHRepDecoder| is wrong. |
michael@0 | 1067 | // The wrapper |RestrictedAccessMonitor| restricts use to the getter |
michael@0 | 1068 | // function rather than the object itself. |
michael@0 | 1069 | private: |
michael@0 | 1070 | class RestrictedAccessMonitor |
michael@0 | 1071 | { |
michael@0 | 1072 | public: |
michael@0 | 1073 | RestrictedAccessMonitor(const char* aName) : |
michael@0 | 1074 | mReentrantMonitor(aName) |
michael@0 | 1075 | { |
michael@0 | 1076 | MOZ_COUNT_CTOR(RestrictedAccessMonitor); |
michael@0 | 1077 | } |
michael@0 | 1078 | ~RestrictedAccessMonitor() |
michael@0 | 1079 | { |
michael@0 | 1080 | MOZ_COUNT_DTOR(RestrictedAccessMonitor); |
michael@0 | 1081 | } |
michael@0 | 1082 | |
michael@0 | 1083 | // Returns a ref to the reentrant monitor |
michael@0 | 1084 | ReentrantMonitor& GetReentrantMonitor() { |
michael@0 | 1085 | return mReentrantMonitor; |
michael@0 | 1086 | } |
michael@0 | 1087 | private: |
michael@0 | 1088 | ReentrantMonitor mReentrantMonitor; |
michael@0 | 1089 | }; |
michael@0 | 1090 | |
michael@0 | 1091 | // The |RestrictedAccessMonitor| member object. |
michael@0 | 1092 | RestrictedAccessMonitor mReentrantMonitor; |
michael@0 | 1093 | |
michael@0 | 1094 | protected: |
michael@0 | 1095 | // Data about MediaStreams that are being fed by this decoder. |
michael@0 | 1096 | nsTArray<OutputStreamData> mOutputStreams; |
michael@0 | 1097 | // The SourceMediaStream we are using to feed the mOutputStreams. This stream |
michael@0 | 1098 | // is never exposed outside the decoder. |
michael@0 | 1099 | // Only written on the main thread while holding the monitor. Therefore it |
michael@0 | 1100 | // can be read on any thread while holding the monitor, or on the main thread |
michael@0 | 1101 | // without holding the monitor. |
michael@0 | 1102 | nsAutoPtr<DecodedStreamData> mDecodedStream; |
michael@0 | 1103 | |
michael@0 | 1104 | // True if this decoder is in dormant state. |
michael@0 | 1105 | // Should be true only when PlayState is PLAY_STATE_LOADING. |
michael@0 | 1106 | bool mIsDormant; |
michael@0 | 1107 | |
michael@0 | 1108 | // True if this decoder is exiting from dormant state. |
michael@0 | 1109 | // Should be true only when PlayState is PLAY_STATE_LOADING. |
michael@0 | 1110 | bool mIsExitingDormant; |
michael@0 | 1111 | |
michael@0 | 1112 | // Set to one of the valid play states. |
michael@0 | 1113 | // This can only be changed on the main thread while holding the decoder |
michael@0 | 1114 | // monitor. Thus, it can be safely read while holding the decoder monitor |
michael@0 | 1115 | // OR on the main thread. |
michael@0 | 1116 | // Any change to the state on the main thread must call NotifyAll on the |
michael@0 | 1117 | // monitor so the decode thread can wake up. |
michael@0 | 1118 | PlayState mPlayState; |
michael@0 | 1119 | |
michael@0 | 1120 | // The state to change to after a seek or load operation. |
michael@0 | 1121 | // This can only be changed on the main thread while holding the decoder |
michael@0 | 1122 | // monitor. Thus, it can be safely read while holding the decoder monitor |
michael@0 | 1123 | // OR on the main thread. |
michael@0 | 1124 | // Any change to the state must call NotifyAll on the monitor. |
michael@0 | 1125 | // This can only be PLAY_STATE_PAUSED or PLAY_STATE_PLAYING. |
michael@0 | 1126 | PlayState mNextState; |
michael@0 | 1127 | |
michael@0 | 1128 | // Position to seek to when the seek notification is received by the |
michael@0 | 1129 | // decode thread. |
michael@0 | 1130 | // This can only be changed on the main thread while holding the decoder |
michael@0 | 1131 | // monitor. Thus, it can be safely read while holding the decoder monitor |
michael@0 | 1132 | // OR on the main thread. |
michael@0 | 1133 | // If the SeekTarget's IsValid() accessor returns false, then no seek has |
michael@0 | 1134 | // been requested. When a seek is started this is reset to invalid. |
michael@0 | 1135 | SeekTarget mRequestedSeekTarget; |
michael@0 | 1136 | |
michael@0 | 1137 | // True when we have fully loaded the resource and reported that |
michael@0 | 1138 | // to the element (i.e. reached NETWORK_LOADED state). |
michael@0 | 1139 | // Accessed on the main thread only. |
michael@0 | 1140 | bool mCalledResourceLoaded; |
michael@0 | 1141 | |
michael@0 | 1142 | // True when seeking or otherwise moving the play position around in |
michael@0 | 1143 | // such a manner that progress event data is inaccurate. This is set |
michael@0 | 1144 | // during seek and duration operations to prevent the progress indicator |
michael@0 | 1145 | // from jumping around. Read/Write from any thread. Must have decode monitor |
michael@0 | 1146 | // locked before accessing. |
michael@0 | 1147 | bool mIgnoreProgressData; |
michael@0 | 1148 | |
michael@0 | 1149 | // True if the stream is infinite (e.g. a webradio). |
michael@0 | 1150 | bool mInfiniteStream; |
michael@0 | 1151 | |
michael@0 | 1152 | // Start timer to update download progress information. |
michael@0 | 1153 | nsresult StartProgress(); |
michael@0 | 1154 | |
michael@0 | 1155 | // Stop progress information timer. |
michael@0 | 1156 | nsresult StopProgress(); |
michael@0 | 1157 | |
michael@0 | 1158 | // Ensures our media stream has been pinned. |
michael@0 | 1159 | void PinForSeek(); |
michael@0 | 1160 | |
michael@0 | 1161 | // Ensures our media stream has been unpinned. |
michael@0 | 1162 | void UnpinForSeek(); |
michael@0 | 1163 | |
michael@0 | 1164 | // Timer used for updating progress events |
michael@0 | 1165 | nsCOMPtr<nsITimer> mProgressTimer; |
michael@0 | 1166 | |
michael@0 | 1167 | // This should only ever be accessed from the main thread. |
michael@0 | 1168 | // It is set in Init and cleared in Shutdown when the element goes away. |
michael@0 | 1169 | // The decoder does not add a reference the element. |
michael@0 | 1170 | MediaDecoderOwner* mOwner; |
michael@0 | 1171 | |
michael@0 | 1172 | // Counters related to decode and presentation of frames. |
michael@0 | 1173 | FrameStatistics mFrameStats; |
michael@0 | 1174 | |
michael@0 | 1175 | nsRefPtr<VideoFrameContainer> mVideoFrameContainer; |
michael@0 | 1176 | |
michael@0 | 1177 | // Time that the last progress event was fired. Read/Write from the |
michael@0 | 1178 | // main thread only. |
michael@0 | 1179 | TimeStamp mProgressTime; |
michael@0 | 1180 | |
michael@0 | 1181 | // Time that data was last read from the media resource. Used for |
michael@0 | 1182 | // computing if the download has stalled and to rate limit progress events |
michael@0 | 1183 | // when data is arriving slower than PROGRESS_MS. A value of null indicates |
michael@0 | 1184 | // that a stall event has already fired and not to fire another one until |
michael@0 | 1185 | // more data is received. Read/Write from the main thread only. |
michael@0 | 1186 | TimeStamp mDataTime; |
michael@0 | 1187 | |
michael@0 | 1188 | // Data needed to estimate playback data rate. The timeline used for |
michael@0 | 1189 | // this estimate is "decode time" (where the "current time" is the |
michael@0 | 1190 | // time of the last decoded video frame). |
michael@0 | 1191 | MediaChannelStatistics mPlaybackStatistics; |
michael@0 | 1192 | |
michael@0 | 1193 | // True when our media stream has been pinned. We pin the stream |
michael@0 | 1194 | // while seeking. |
michael@0 | 1195 | bool mPinnedForSeek; |
michael@0 | 1196 | |
michael@0 | 1197 | // True if the decoder is being shutdown. At this point all events that |
michael@0 | 1198 | // are currently queued need to return immediately to prevent javascript |
michael@0 | 1199 | // being run that operates on the element and decoder during shutdown. |
michael@0 | 1200 | // Read/Write from the main thread only. |
michael@0 | 1201 | bool mShuttingDown; |
michael@0 | 1202 | |
michael@0 | 1203 | // True if the playback is paused because the playback rate member is 0.0. |
michael@0 | 1204 | bool mPausedForPlaybackRateNull; |
michael@0 | 1205 | |
michael@0 | 1206 | // Be assigned from media element during the initialization and pass to |
michael@0 | 1207 | // AudioStream Class. |
michael@0 | 1208 | dom::AudioChannel mAudioChannel; |
michael@0 | 1209 | |
michael@0 | 1210 | // True if the decoder has been directed to minimize its preroll before |
michael@0 | 1211 | // playback starts. After the first time playback starts, we don't attempt |
michael@0 | 1212 | // to minimize preroll, as we assume the user is likely to keep playing, |
michael@0 | 1213 | // or play the media again. |
michael@0 | 1214 | bool mMinimizePreroll; |
michael@0 | 1215 | }; |
michael@0 | 1216 | |
michael@0 | 1217 | } // namespace mozilla |
michael@0 | 1218 | |
michael@0 | 1219 | #endif |