content/media/mediasource/MediaSource.cpp

changeset 0
6474c204b198
     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

mercurial