1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/mediasource/MediaSource.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,410 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "MediaSource.h" 1.11 + 1.12 +#include "AsyncEventRunner.h" 1.13 +#include "DecoderTraits.h" 1.14 +#include "SourceBuffer.h" 1.15 +#include "SourceBufferList.h" 1.16 +#include "mozilla/ErrorResult.h" 1.17 +#include "mozilla/FloatingPoint.h" 1.18 +#include "mozilla/Preferences.h" 1.19 +#include "mozilla/dom/BindingDeclarations.h" 1.20 +#include "mozilla/dom/HTMLMediaElement.h" 1.21 +#include "mozilla/mozalloc.h" 1.22 +#include "nsContentTypeParser.h" 1.23 +#include "nsDebug.h" 1.24 +#include "nsError.h" 1.25 +#include "nsIEventTarget.h" 1.26 +#include "nsIRunnable.h" 1.27 +#include "nsPIDOMWindow.h" 1.28 +#include "nsStringGlue.h" 1.29 +#include "nsThreadUtils.h" 1.30 +#include "prlog.h" 1.31 + 1.32 +struct JSContext; 1.33 +class JSObject; 1.34 + 1.35 +#ifdef PR_LOGGING 1.36 +PRLogModuleInfo* gMediaSourceLog; 1.37 +#define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__)) 1.38 +#else 1.39 +#define MSE_DEBUG(...) 1.40 +#endif 1.41 + 1.42 +// Arbitrary limit. 1.43 +static const unsigned int MAX_SOURCE_BUFFERS = 16; 1.44 + 1.45 +namespace mozilla { 1.46 + 1.47 +static const char* const gMediaSourceTypes[6] = { 1.48 + "video/webm", 1.49 + "audio/webm", 1.50 +// XXX: Disabled other codecs temporarily to allow WebM testing. For now, set 1.51 +// the developer-only media.mediasource.ignore_codecs pref to true to test other 1.52 +// codecs, and expect things to be broken. 1.53 +#if 0 1.54 + "video/mp4", 1.55 + "audio/mp4", 1.56 + "audio/mpeg", 1.57 +#endif 1.58 + nullptr 1.59 +}; 1.60 + 1.61 +static nsresult 1.62 +IsTypeSupported(const nsAString& aType) 1.63 +{ 1.64 + if (aType.IsEmpty()) { 1.65 + return NS_ERROR_DOM_INVALID_ACCESS_ERR; 1.66 + } 1.67 + // TODO: Further restrict this to formats in the spec. 1.68 + nsContentTypeParser parser(aType); 1.69 + nsAutoString mimeType; 1.70 + nsresult rv = parser.GetType(mimeType); 1.71 + if (NS_FAILED(rv)) { 1.72 + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; 1.73 + } 1.74 + bool found = false; 1.75 + for (uint32_t i = 0; gMediaSourceTypes[i]; ++i) { 1.76 + if (mimeType.EqualsASCII(gMediaSourceTypes[i])) { 1.77 + found = true; 1.78 + break; 1.79 + } 1.80 + } 1.81 + if (!found) { 1.82 + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; 1.83 + } 1.84 + if (Preferences::GetBool("media.mediasource.ignore_codecs", false)) { 1.85 + return NS_OK; 1.86 + } 1.87 + // Check aType against HTMLMediaElement list of MIME types. Since we've 1.88 + // already restricted the container format, this acts as a specific check 1.89 + // of any specified "codecs" parameter of aType. 1.90 + if (dom::HTMLMediaElement::GetCanPlay(aType) == CANPLAY_NO) { 1.91 + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; 1.92 + } 1.93 + return NS_OK; 1.94 +} 1.95 + 1.96 +namespace dom { 1.97 + 1.98 +/* static */ already_AddRefed<MediaSource> 1.99 +MediaSource::Constructor(const GlobalObject& aGlobal, 1.100 + ErrorResult& aRv) 1.101 +{ 1.102 + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports()); 1.103 + if (!window) { 1.104 + aRv.Throw(NS_ERROR_UNEXPECTED); 1.105 + return nullptr; 1.106 + } 1.107 + 1.108 + nsRefPtr<MediaSource> mediaSource = new MediaSource(window); 1.109 + return mediaSource.forget(); 1.110 +} 1.111 + 1.112 +SourceBufferList* 1.113 +MediaSource::SourceBuffers() 1.114 +{ 1.115 + MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, mSourceBuffers->IsEmpty()); 1.116 + return mSourceBuffers; 1.117 +} 1.118 + 1.119 +SourceBufferList* 1.120 +MediaSource::ActiveSourceBuffers() 1.121 +{ 1.122 + MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, mActiveSourceBuffers->IsEmpty()); 1.123 + return mActiveSourceBuffers; 1.124 +} 1.125 + 1.126 +MediaSourceReadyState 1.127 +MediaSource::ReadyState() 1.128 +{ 1.129 + return mReadyState; 1.130 +} 1.131 + 1.132 +double 1.133 +MediaSource::Duration() 1.134 +{ 1.135 + if (mReadyState == MediaSourceReadyState::Closed) { 1.136 + return UnspecifiedNaN<double>(); 1.137 + } 1.138 + return mDuration; 1.139 +} 1.140 + 1.141 +void 1.142 +MediaSource::SetDuration(double aDuration, ErrorResult& aRv) 1.143 +{ 1.144 + if (aDuration < 0 || IsNaN(aDuration)) { 1.145 + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); 1.146 + return; 1.147 + } 1.148 + if (mReadyState != MediaSourceReadyState::Open || 1.149 + mSourceBuffers->AnyUpdating()) { 1.150 + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1.151 + return; 1.152 + } 1.153 + DurationChange(aDuration, aRv); 1.154 +} 1.155 + 1.156 +already_AddRefed<SourceBuffer> 1.157 +MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv) 1.158 +{ 1.159 + nsresult rv = mozilla::IsTypeSupported(aType); 1.160 + MSE_DEBUG("MediaSource::AddSourceBuffer(Type=%s) -> %x", NS_ConvertUTF16toUTF8(aType).get(), rv); 1.161 + if (NS_FAILED(rv)) { 1.162 + aRv.Throw(rv); 1.163 + return nullptr; 1.164 + } 1.165 + if (mSourceBuffers->Length() >= MAX_SOURCE_BUFFERS) { 1.166 + aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR); 1.167 + return nullptr; 1.168 + } 1.169 + if (mReadyState != MediaSourceReadyState::Open) { 1.170 + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1.171 + return nullptr; 1.172 + } 1.173 + nsContentTypeParser parser(aType); 1.174 + nsAutoString mimeType; 1.175 + rv = parser.GetType(mimeType); 1.176 + if (NS_FAILED(rv)) { 1.177 + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 1.178 + return nullptr; 1.179 + } 1.180 + nsRefPtr<SourceBuffer> sourceBuffer = SourceBuffer::Create(this, NS_ConvertUTF16toUTF8(mimeType)); 1.181 + if (!sourceBuffer) { 1.182 + aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here 1.183 + return nullptr; 1.184 + } 1.185 + mSourceBuffers->Append(sourceBuffer); 1.186 + mActiveSourceBuffers->Append(sourceBuffer); 1.187 + MSE_DEBUG("%p AddSourceBuffer(Type=%s) -> %p", this, 1.188 + NS_ConvertUTF16toUTF8(mimeType).get(), sourceBuffer.get()); 1.189 + return sourceBuffer.forget(); 1.190 +} 1.191 + 1.192 +void 1.193 +MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv) 1.194 +{ 1.195 + SourceBuffer* sourceBuffer = &aSourceBuffer; 1.196 + MSE_DEBUG("%p RemoveSourceBuffer(Buffer=%p)", this, sourceBuffer); 1.197 + if (!mSourceBuffers->Contains(sourceBuffer)) { 1.198 + aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); 1.199 + return; 1.200 + } 1.201 + if (sourceBuffer->Updating()) { 1.202 + // TODO: 1.203 + // abort stream append loop (if running) 1.204 + // set updating to false 1.205 + // fire "abort" at sourceBuffer 1.206 + // fire "updateend" at sourceBuffer 1.207 + } 1.208 + // TODO: 1.209 + // For all sourceBuffer audioTracks, videoTracks, textTracks: 1.210 + // set sourceBuffer to null 1.211 + // remove sourceBuffer video, audio, text Tracks from MediaElement tracks 1.212 + // remove sourceBuffer video, audio, text Tracks and fire "removetrack" at affected lists 1.213 + // fire "removetrack" at modified MediaElement track lists 1.214 + // If removed enabled/selected, fire "change" at affected MediaElement list. 1.215 + if (mActiveSourceBuffers->Contains(sourceBuffer)) { 1.216 + mActiveSourceBuffers->Remove(sourceBuffer); 1.217 + } 1.218 + mSourceBuffers->Remove(sourceBuffer); 1.219 + // TODO: Free all resources associated with sourceBuffer 1.220 +} 1.221 + 1.222 +void 1.223 +MediaSource::EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv) 1.224 +{ 1.225 + MSE_DEBUG("%p EndOfStream(Error=%u)", this, aError.WasPassed() ? uint32_t(aError.Value()) : 0); 1.226 + if (mReadyState != MediaSourceReadyState::Open || 1.227 + mSourceBuffers->AnyUpdating()) { 1.228 + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1.229 + return; 1.230 + } 1.231 + 1.232 + SetReadyState(MediaSourceReadyState::Ended); 1.233 + mSourceBuffers->Ended(); 1.234 + if (!aError.WasPassed()) { 1.235 + // TODO: 1.236 + // Run duration change algorithm. 1.237 + // DurationChange(highestDurationOfSourceBuffers, aRv); 1.238 + // if (aRv.Failed()) { 1.239 + // return; 1.240 + // } 1.241 + // Notify media element that all data is now available. 1.242 + return; 1.243 + } 1.244 + switch (aError.Value()) { 1.245 + case MediaSourceEndOfStreamError::Network: 1.246 + // TODO: If media element has a readyState of: 1.247 + // HAVE_NOTHING -> run resource fetch algorithm 1.248 + // > HAVE_NOTHING -> run "interrupted" steps of resource fetch 1.249 + break; 1.250 + case MediaSourceEndOfStreamError::Decode: 1.251 + // TODO: If media element has a readyState of: 1.252 + // HAVE_NOTHING -> run "unsupported" steps of resource fetch 1.253 + // > HAVE_NOTHING -> run "corrupted" steps of resource fetch 1.254 + break; 1.255 + default: 1.256 + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); 1.257 + } 1.258 +} 1.259 + 1.260 +/* static */ bool 1.261 +MediaSource::IsTypeSupported(const GlobalObject&, const nsAString& aType) 1.262 +{ 1.263 +#ifdef PR_LOGGING 1.264 + if (!gMediaSourceLog) { 1.265 + gMediaSourceLog = PR_NewLogModule("MediaSource"); 1.266 + } 1.267 +#endif 1.268 + nsresult rv = mozilla::IsTypeSupported(aType); 1.269 + MSE_DEBUG("MediaSource::IsTypeSupported(Type=%s) -> %x", NS_ConvertUTF16toUTF8(aType).get(), rv); 1.270 + return NS_SUCCEEDED(rv); 1.271 +} 1.272 + 1.273 +bool 1.274 +MediaSource::Attach(MediaSourceDecoder* aDecoder) 1.275 +{ 1.276 + MSE_DEBUG("%p Attaching decoder %p owner %p", this, aDecoder, aDecoder->GetOwner()); 1.277 + MOZ_ASSERT(aDecoder); 1.278 + if (mReadyState != MediaSourceReadyState::Closed) { 1.279 + return false; 1.280 + } 1.281 + mDecoder = aDecoder; 1.282 + mDecoder->AttachMediaSource(this); 1.283 + SetReadyState(MediaSourceReadyState::Open); 1.284 + return true; 1.285 +} 1.286 + 1.287 +void 1.288 +MediaSource::Detach() 1.289 +{ 1.290 + MSE_DEBUG("%p Detaching decoder %p owner %p", this, mDecoder.get(), mDecoder->GetOwner()); 1.291 + MOZ_ASSERT(mDecoder); 1.292 + mDecoder->DetachMediaSource(); 1.293 + mDecoder = nullptr; 1.294 + mDuration = UnspecifiedNaN<double>(); 1.295 + mActiveSourceBuffers->Clear(); 1.296 + mSourceBuffers->Clear(); 1.297 + SetReadyState(MediaSourceReadyState::Closed); 1.298 +} 1.299 + 1.300 +MediaSource::MediaSource(nsPIDOMWindow* aWindow) 1.301 + : DOMEventTargetHelper(aWindow) 1.302 + , mDuration(UnspecifiedNaN<double>()) 1.303 + , mDecoder(nullptr) 1.304 + , mReadyState(MediaSourceReadyState::Closed) 1.305 +{ 1.306 + mSourceBuffers = new SourceBufferList(this); 1.307 + mActiveSourceBuffers = new SourceBufferList(this); 1.308 + 1.309 +#ifdef PR_LOGGING 1.310 + if (!gMediaSourceLog) { 1.311 + gMediaSourceLog = PR_NewLogModule("MediaSource"); 1.312 + } 1.313 +#endif 1.314 +} 1.315 + 1.316 +void 1.317 +MediaSource::SetReadyState(MediaSourceReadyState aState) 1.318 +{ 1.319 + MOZ_ASSERT(aState != mReadyState); 1.320 + MSE_DEBUG("%p SetReadyState old=%d new=%d", this, mReadyState, aState); 1.321 + 1.322 + MediaSourceReadyState oldState = mReadyState; 1.323 + mReadyState = aState; 1.324 + 1.325 + if (mReadyState == MediaSourceReadyState::Open && 1.326 + (oldState == MediaSourceReadyState::Closed || 1.327 + oldState == MediaSourceReadyState::Ended)) { 1.328 + QueueAsyncSimpleEvent("sourceopen"); 1.329 + return; 1.330 + } 1.331 + 1.332 + if (mReadyState == MediaSourceReadyState::Ended && 1.333 + oldState == MediaSourceReadyState::Open) { 1.334 + QueueAsyncSimpleEvent("sourceended"); 1.335 + return; 1.336 + } 1.337 + 1.338 + if (mReadyState == MediaSourceReadyState::Closed && 1.339 + (oldState == MediaSourceReadyState::Open || 1.340 + oldState == MediaSourceReadyState::Ended)) { 1.341 + QueueAsyncSimpleEvent("sourceclose"); 1.342 + return; 1.343 + } 1.344 + 1.345 + NS_WARNING("Invalid MediaSource readyState transition"); 1.346 +} 1.347 + 1.348 +void 1.349 +MediaSource::DispatchSimpleEvent(const char* aName) 1.350 +{ 1.351 + MSE_DEBUG("%p Dispatching event %s to MediaSource", this, aName); 1.352 + DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName)); 1.353 +} 1.354 + 1.355 +void 1.356 +MediaSource::QueueAsyncSimpleEvent(const char* aName) 1.357 +{ 1.358 + MSE_DEBUG("%p Queuing event %s to MediaSource", this, aName); 1.359 + nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<MediaSource>(this, aName); 1.360 + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.361 +} 1.362 + 1.363 +void 1.364 +MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv) 1.365 +{ 1.366 + if (mDuration == aNewDuration) { 1.367 + return; 1.368 + } 1.369 + double oldDuration = mDuration; 1.370 + mDuration = aNewDuration; 1.371 + if (aNewDuration < oldDuration) { 1.372 + mSourceBuffers->Remove(aNewDuration, oldDuration, aRv); 1.373 + if (aRv.Failed()) { 1.374 + return; 1.375 + } 1.376 + } 1.377 + // TODO: If partial audio frames/text cues exist, clamp duration based on mSourceBuffers. 1.378 + // TODO: Update media element's duration and run element's duration change algorithm. 1.379 +} 1.380 + 1.381 +nsPIDOMWindow* 1.382 +MediaSource::GetParentObject() const 1.383 +{ 1.384 + return GetOwner(); 1.385 +} 1.386 + 1.387 +JSObject* 1.388 +MediaSource::WrapObject(JSContext* aCx) 1.389 +{ 1.390 + return MediaSourceBinding::Wrap(aCx, this); 1.391 +} 1.392 + 1.393 +void 1.394 +MediaSource::NotifyEvicted(double aStart, double aEnd) 1.395 +{ 1.396 + // Cycle through all SourceBuffers and tell them to evict data in 1.397 + // the given range. 1.398 + mSourceBuffers->Evict(aStart, aEnd); 1.399 +} 1.400 + 1.401 +NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaSource, DOMEventTargetHelper, 1.402 + mSourceBuffers, mActiveSourceBuffers) 1.403 + 1.404 +NS_IMPL_ADDREF_INHERITED(MediaSource, DOMEventTargetHelper) 1.405 +NS_IMPL_RELEASE_INHERITED(MediaSource, DOMEventTargetHelper) 1.406 + 1.407 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaSource) 1.408 + NS_INTERFACE_MAP_ENTRY(mozilla::dom::MediaSource) 1.409 +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 1.410 + 1.411 +} // namespace dom 1.412 + 1.413 +} // namespace mozilla