michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "MediaResource.h" michael@0: #include "RtspMediaResource.h" michael@0: michael@0: #include "mozilla/Mutex.h" michael@0: #include "nsDebug.h" michael@0: #include "MediaDecoder.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIFileChannel.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIRequestObserver.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsCrossSiteListenerProxy.h" michael@0: #include "mozilla/dom/HTMLMediaElement.h" michael@0: #include "nsError.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsHostObjectProtocolHandler.h" michael@0: #include michael@0: #include "nsProxyRelease.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gMediaResourceLog; michael@0: #define RESOURCE_LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \ michael@0: (msg, ##__VA_ARGS__)) michael@0: // Debug logging macro with object pointer and class name. michael@0: #define CMLOG(msg, ...) \ michael@0: RESOURCE_LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__) michael@0: #else michael@0: #define RESOURCE_LOG(msg, ...) michael@0: #define CMLOG(msg, ...) michael@0: #endif michael@0: michael@0: static const uint32_t HTTP_OK_CODE = 200; michael@0: static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206; michael@0: michael@0: namespace mozilla { michael@0: michael@0: void michael@0: MediaResource::Destroy() michael@0: { michael@0: // If we're being destroyed on a non-main thread, we AddRef again and michael@0: // use a proxy to release the MediaResource on the main thread, where michael@0: // the MediaResource is deleted. This ensures we only delete the michael@0: // MediaResource on the main thread. michael@0: if (!NS_IsMainThread()) { michael@0: nsCOMPtr mainThread = do_GetMainThread(); michael@0: NS_ENSURE_TRUE_VOID(mainThread); michael@0: nsRefPtr doomed(this); michael@0: if (NS_FAILED(NS_ProxyRelease(mainThread, doomed, true))) { michael@0: NS_WARNING("Failed to proxy release to main thread!"); michael@0: } michael@0: } else { michael@0: delete this; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(MediaResource) michael@0: NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy()) michael@0: NS_IMPL_QUERY_INTERFACE0(MediaResource) michael@0: michael@0: ChannelMediaResource::ChannelMediaResource(MediaDecoder* aDecoder, michael@0: nsIChannel* aChannel, michael@0: nsIURI* aURI, michael@0: const nsACString& aContentType) michael@0: : BaseMediaResource(aDecoder, aChannel, aURI, aContentType), michael@0: mOffset(0), mSuspendCount(0), michael@0: mReopenOnError(false), mIgnoreClose(false), michael@0: mCacheStream(MOZ_THIS_IN_INITIALIZER_LIST()), michael@0: mLock("ChannelMediaResource.mLock"), michael@0: mIgnoreResume(false), michael@0: mSeekingForMetadata(false), michael@0: mIsTransportSeekable(true) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gMediaResourceLog) { michael@0: gMediaResourceLog = PR_NewLogModule("MediaResource"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: ChannelMediaResource::~ChannelMediaResource() michael@0: { michael@0: if (mListener) { michael@0: // Kill its reference to us since we're going away michael@0: mListener->Revoke(); michael@0: } michael@0: } michael@0: michael@0: // ChannelMediaResource::Listener just observes the channel and michael@0: // forwards notifications to the ChannelMediaResource. We use multiple michael@0: // listener objects so that when we open a new stream for a seek we can michael@0: // disconnect the old listener from the ChannelMediaResource and hook up michael@0: // a new listener, so notifications from the old channel are discarded michael@0: // and don't confuse us. michael@0: NS_IMPL_ISUPPORTS(ChannelMediaResource::Listener, michael@0: nsIRequestObserver, nsIStreamListener, nsIChannelEventSink, michael@0: nsIInterfaceRequestor) michael@0: michael@0: nsresult michael@0: ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest, michael@0: nsISupports* aContext) michael@0: { michael@0: if (!mResource) michael@0: return NS_OK; michael@0: return mResource->OnStartRequest(aRequest); michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsresult aStatus) michael@0: { michael@0: if (!mResource) michael@0: return NS_OK; michael@0: return mResource->OnStopRequest(aRequest, aStatus); michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsIInputStream* aStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: if (!mResource) michael@0: return NS_OK; michael@0: return mResource->OnDataAvailable(aRequest, aStream, aCount); michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::Listener::AsyncOnChannelRedirect(nsIChannel* aOldChannel, michael@0: nsIChannel* aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback* cb) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (mResource) michael@0: rv = mResource->OnChannelRedirect(aOldChannel, aNewChannel, aFlags); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: cb->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: return QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::OnStartRequest(nsIRequest* aRequest) michael@0: { michael@0: NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!"); michael@0: michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); michael@0: nsresult status; michael@0: nsresult rv = aRequest->GetStatus(&status); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (status == NS_BINDING_ABORTED) { michael@0: // Request was aborted before we had a chance to receive any data, or michael@0: // even an OnStartRequest(). Close the channel. This is important, as michael@0: // we don't want to mess up our state, as if we're cloned that would michael@0: // cause the clone to copy incorrect metadata (like whether we're michael@0: // infinite for example). michael@0: CloseChannel(); michael@0: return status; michael@0: } michael@0: michael@0: if (element->ShouldCheckAllowOrigin()) { michael@0: // If the request was cancelled by nsCORSListenerProxy due to failing michael@0: // the CORS security check, send an error through to the media element. michael@0: if (status == NS_ERROR_DOM_BAD_URI) { michael@0: mDecoder->NetworkError(); michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr hc = do_QueryInterface(aRequest); michael@0: bool seekable = false; michael@0: if (hc) { michael@0: uint32_t responseStatus = 0; michael@0: hc->GetResponseStatus(&responseStatus); michael@0: bool succeeded = false; michael@0: hc->GetRequestSucceeded(&succeeded); michael@0: michael@0: if (!succeeded && NS_SUCCEEDED(status)) { michael@0: // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error. michael@0: // We might get this on a seek. michael@0: // (Note that lower-level errors indicated by NS_FAILED(status) are michael@0: // handled in OnStopRequest.) michael@0: // A 416 error should treated as EOF here... it's possible michael@0: // that we don't get Content-Length, we read N bytes, then we michael@0: // suspend and resume, the resume reopens the channel and we seek to michael@0: // offset N, but there are no more bytes, so we get a 416 michael@0: // "Requested Range Not Satisfiable". michael@0: if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) { michael@0: // OnStopRequest will not be fired, so we need to do some of its michael@0: // work here. michael@0: mCacheStream.NotifyDataEnded(status); michael@0: } else { michael@0: mDecoder->NetworkError(); michael@0: } michael@0: michael@0: // This disconnects our listener so we don't get any more data. We michael@0: // certainly don't want an error page to end up in our cache! michael@0: CloseChannel(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString ranges; michael@0: hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"), michael@0: ranges); michael@0: bool acceptsRanges = ranges.EqualsLiteral("bytes"); michael@0: // True if this channel will not return an unbounded amount of data michael@0: bool dataIsBounded = false; michael@0: michael@0: int64_t contentLength = -1; michael@0: hc->GetContentLength(&contentLength); michael@0: if (contentLength >= 0 && responseStatus == HTTP_OK_CODE) { michael@0: // "OK" status means Content-Length is for the whole resource. michael@0: // Since that's bounded, we know we have a finite-length resource. michael@0: dataIsBounded = true; michael@0: } michael@0: michael@0: if (mOffset == 0) { michael@0: // Look for duration headers from known Ogg content systems. michael@0: // In the case of multiple options for obtaining the duration michael@0: // the order of precedence is: michael@0: // 1) The Media resource metadata if possible (done by the decoder itself). michael@0: // 2) Content-Duration message header. michael@0: // 3) X-AMZ-Meta-Content-Duration. michael@0: // 4) X-Content-Duration. michael@0: // 5) Perform a seek in the decoder to find the value. michael@0: nsAutoCString durationText; michael@0: nsresult ec = NS_OK; michael@0: rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText); michael@0: if (NS_FAILED(rv)) { michael@0: rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText); michael@0: } michael@0: michael@0: // If there is a Content-Duration header with a valid value, record michael@0: // the duration. michael@0: if (NS_SUCCEEDED(rv)) { michael@0: double duration = durationText.ToDouble(&ec); michael@0: if (ec == NS_OK && duration >= 0) { michael@0: mDecoder->SetDuration(duration); michael@0: // We know the resource must be bounded. michael@0: dataIsBounded = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Assume Range requests have a bounded upper limit unless the michael@0: // Content-Range header tells us otherwise. michael@0: bool boundedSeekLimit = true; michael@0: // Check response code for byte-range requests (seeking, chunk requests). michael@0: if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) { michael@0: // Parse Content-Range header. michael@0: int64_t rangeStart = 0; michael@0: int64_t rangeEnd = 0; michael@0: int64_t rangeTotal = 0; michael@0: rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal); michael@0: if (NS_FAILED(rv)) { michael@0: // Content-Range header text should be parse-able. michael@0: CMLOG("Error processing \'Content-Range' for " michael@0: "HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]", michael@0: rv, hc.get(), mDecoder); michael@0: mDecoder->NetworkError(); michael@0: CloseChannel(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Give some warnings if the ranges are unexpected. michael@0: // XXX These could be error conditions. michael@0: NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart, michael@0: "response range start does not match request"); michael@0: NS_WARN_IF_FALSE(mOffset == rangeStart, michael@0: "response range start does not match current offset"); michael@0: NS_WARN_IF_FALSE(mByteRange.mEnd == rangeEnd, michael@0: "response range end does not match request"); michael@0: // Notify media cache about the length and start offset of data received. michael@0: // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage. michael@0: // For now, tell the decoder that the stream is infinite. michael@0: if (rangeTotal == -1) { michael@0: boundedSeekLimit = false; michael@0: } else { michael@0: mCacheStream.NotifyDataLength(rangeTotal); michael@0: } michael@0: mCacheStream.NotifyDataStarted(rangeStart); michael@0: michael@0: mOffset = rangeStart; michael@0: // We received 'Content-Range', so the server accepts range requests. michael@0: acceptsRanges = true; michael@0: } else if (((mOffset > 0) || !mByteRange.IsNull()) michael@0: && (responseStatus == HTTP_OK_CODE)) { michael@0: // If we get an OK response but we were seeking, or requesting a byte michael@0: // range, then we have to assume that seeking doesn't work. We also need michael@0: // to tell the cache that it's getting data for the start of the stream. michael@0: mCacheStream.NotifyDataStarted(0); michael@0: mOffset = 0; michael@0: michael@0: // The server claimed it supported range requests. It lied. michael@0: acceptsRanges = false; michael@0: } else if (mOffset == 0 && michael@0: (responseStatus == HTTP_OK_CODE || michael@0: responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) { michael@0: if (contentLength >= 0) { michael@0: mCacheStream.NotifyDataLength(contentLength); michael@0: } michael@0: } michael@0: // XXX we probably should examine the Content-Range header in case michael@0: // the server gave us a range which is not quite what we asked for michael@0: michael@0: // If we get an HTTP_OK_CODE response to our byte range request, michael@0: // and the server isn't sending Accept-Ranges:bytes then we don't michael@0: // support seeking. michael@0: seekable = michael@0: responseStatus == HTTP_PARTIAL_RESPONSE_CODE || acceptsRanges; michael@0: if (seekable && boundedSeekLimit) { michael@0: // If range requests are supported, and we did not see an unbounded michael@0: // upper range limit, we assume the resource is bounded. michael@0: dataIsBounded = true; michael@0: } michael@0: michael@0: mDecoder->SetInfinite(!dataIsBounded); michael@0: } michael@0: mDecoder->SetTransportSeekable(seekable); michael@0: mCacheStream.SetTransportSeekable(seekable); michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mIsTransportSeekable = seekable; michael@0: mChannelStatistics->Start(); michael@0: } michael@0: michael@0: mReopenOnError = false; michael@0: // If we are seeking to get metadata, because we are playing an OGG file, michael@0: // ignore if the channel gets closed without us suspending it explicitly. We michael@0: // don't want to tell the element that the download has finished whereas we michael@0: // just happended to have reached the end of the media while seeking. michael@0: mIgnoreClose = mSeekingForMetadata; michael@0: michael@0: if (mSuspendCount > 0) { michael@0: // Re-suspend the channel if it needs to be suspended michael@0: // No need to call PossiblySuspend here since the channel is michael@0: // definitely in the right state for us in OnStartRequest. michael@0: mChannel->Suspend(); michael@0: mIgnoreResume = false; michael@0: } michael@0: michael@0: // Fires an initial progress event and sets up the stall counter so stall events michael@0: // fire if no download occurs within the required time frame. michael@0: mDecoder->Progress(false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: ChannelMediaResource::IsTransportSeekable() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: return mIsTransportSeekable; michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan, michael@0: int64_t& aRangeStart, michael@0: int64_t& aRangeEnd, michael@0: int64_t& aRangeTotal) michael@0: { michael@0: NS_ENSURE_ARG(aHttpChan); michael@0: michael@0: nsAutoCString rangeStr; michael@0: nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), michael@0: rangeStr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE); michael@0: michael@0: // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000. michael@0: int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" ")); michael@0: int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos); michael@0: int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos); michael@0: michael@0: nsAutoCString aRangeStartText; michael@0: rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1)); michael@0: aRangeStart = aRangeStartText.ToInteger64(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE); michael@0: michael@0: nsAutoCString aRangeEndText; michael@0: rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1)); michael@0: aRangeEnd = aRangeEndText.ToInteger64(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE); michael@0: michael@0: nsAutoCString aRangeTotalText; michael@0: rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1)); michael@0: if (aRangeTotalText[0] == '*') { michael@0: aRangeTotal = -1; michael@0: } else { michael@0: aRangeTotal = aRangeTotalText.ToInteger64(&rv); michael@0: NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]", michael@0: aRangeStart, aRangeEnd, aRangeTotal, mDecoder); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) michael@0: { michael@0: NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!"); michael@0: NS_ASSERTION(mSuspendCount == 0, michael@0: "How can OnStopRequest fire while we're suspended?"); michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mChannelStatistics->Stop(); michael@0: } michael@0: michael@0: // Note that aStatus might have succeeded --- this might be a normal close michael@0: // --- even in situations where the server cut us off because we were michael@0: // suspended. So we need to "reopen on error" in that case too. The only michael@0: // cases where we don't need to reopen are when *we* closed the stream. michael@0: // But don't reopen if we need to seek and we don't think we can... that would michael@0: // cause us to just re-read the stream, which would be really bad. michael@0: if (mReopenOnError && michael@0: aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED && michael@0: (mOffset == 0 || mCacheStream.IsTransportSeekable())) { michael@0: // If the stream did close normally, then if the server is seekable we'll michael@0: // just seek to the end of the resource and get an HTTP 416 error because michael@0: // there's nothing there, so this isn't bad. michael@0: nsresult rv = CacheClientSeek(mOffset, false); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return rv; michael@0: // If the reopen/reseek fails, just fall through and treat this michael@0: // error as fatal. michael@0: } michael@0: michael@0: if (!mIgnoreClose) { michael@0: mCacheStream.NotifyDataEnded(aStatus); michael@0: michael@0: // Move this request back into the foreground. This is necessary for michael@0: // requests owned by video documents to ensure the load group fires michael@0: // OnStopRequest when restoring from session history. michael@0: nsLoadFlags loadFlags; michael@0: DebugOnly rv = mChannel->GetLoadFlags(&loadFlags); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!"); michael@0: michael@0: if (loadFlags & nsIRequest::LOAD_BACKGROUND) { michael@0: ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew, michael@0: uint32_t aFlags) michael@0: { michael@0: mChannel = aNew; michael@0: SetupChannelHeaders(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct CopySegmentClosure { michael@0: nsCOMPtr mPrincipal; michael@0: ChannelMediaResource* mResource; michael@0: }; michael@0: michael@0: NS_METHOD michael@0: ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream, michael@0: void *aClosure, michael@0: const char *aFromSegment, michael@0: uint32_t aToOffset, michael@0: uint32_t aCount, michael@0: uint32_t *aWriteCount) michael@0: { michael@0: CopySegmentClosure* closure = static_cast(aClosure); michael@0: michael@0: closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset); michael@0: michael@0: // Keep track of where we're up to. michael@0: RESOURCE_LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add " michael@0: "[%d] bytes for decoder[%p]", michael@0: closure->mResource, closure->mResource->mOffset, aCount, michael@0: closure->mResource->mDecoder); michael@0: closure->mResource->mOffset += aCount; michael@0: michael@0: closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment, michael@0: closure->mPrincipal); michael@0: *aWriteCount = aCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest, michael@0: nsIInputStream* aStream, michael@0: uint32_t aCount) michael@0: { michael@0: NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!"); michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mChannelStatistics->AddBytes(aCount); michael@0: } michael@0: michael@0: CopySegmentClosure closure; michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: if (secMan && mChannel) { michael@0: secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal)); michael@0: } michael@0: closure.mResource = this; michael@0: michael@0: uint32_t count = aCount; michael@0: while (count > 0) { michael@0: uint32_t read; michael@0: nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count, michael@0: &read); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: NS_ASSERTION(read > 0, "Read 0 bytes while data was available?"); michael@0: count -= read; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: if (!mChannelStatistics) { michael@0: mChannelStatistics = new MediaChannelStatistics(); michael@0: } michael@0: michael@0: nsresult rv = mCacheStream.Init(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: NS_ASSERTION(mOffset == 0, "Who set mOffset already?"); michael@0: michael@0: if (!mChannel) { michael@0: // When we're a clone, the decoder might ask us to Open even though michael@0: // we haven't established an mChannel (because we might not need one) michael@0: NS_ASSERTION(!aStreamListener, michael@0: "Should have already been given a channel if we're to return a stream listener"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return OpenChannel(aStreamListener); michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER); michael@0: NS_ASSERTION(!mListener, "Listener should have been removed by now"); michael@0: michael@0: if (aStreamListener) { michael@0: *aStreamListener = nullptr; michael@0: } michael@0: michael@0: if (mByteRange.IsNull()) { michael@0: // We're not making a byte range request, so set the content length, michael@0: // if it's available as an HTTP header. This ensures that MediaResource michael@0: // wrapping objects for platform libraries that expect to know michael@0: // the length of a resource can get it before OnStartRequest() fires. michael@0: nsCOMPtr hc = do_QueryInterface(mChannel); michael@0: if (hc) { michael@0: int64_t cl = -1; michael@0: if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) { michael@0: mCacheStream.NotifyDataLength(cl); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mListener = new Listener(this); michael@0: NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: if (aStreamListener) { michael@0: *aStreamListener = mListener; michael@0: NS_ADDREF(*aStreamListener); michael@0: } else { michael@0: mChannel->SetNotificationCallbacks(mListener.get()); michael@0: michael@0: nsCOMPtr listener = mListener.get(); michael@0: michael@0: // Ensure that if we're loading cross domain, that the server is sending michael@0: // an authorizing Access-Control header. michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); michael@0: if (element->ShouldCheckAllowOrigin()) { michael@0: nsRefPtr crossSiteListener = michael@0: new nsCORSListenerProxy(mListener, michael@0: element->NodePrincipal(), michael@0: false); michael@0: nsresult rv = crossSiteListener->Init(mChannel); michael@0: listener = crossSiteListener; michael@0: NS_ENSURE_TRUE(crossSiteListener, NS_ERROR_OUT_OF_MEMORY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: nsresult rv = nsContentUtils::GetSecurityManager()-> michael@0: CheckLoadURIWithPrincipal(element->NodePrincipal(), michael@0: mURI, michael@0: nsIScriptSecurityManager::STANDARD); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: SetupChannelHeaders(); michael@0: michael@0: nsresult rv = mChannel->AsyncOpen(listener, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Tell the media element that we are fetching data from a channel. michael@0: element->DownloadResumed(true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void ChannelMediaResource::SetupChannelHeaders() michael@0: { michael@0: // Always use a byte range request even if we're reading from the start michael@0: // of the resource. michael@0: // This enables us to detect if the stream supports byte range michael@0: // requests, and therefore seeking, early. michael@0: nsCOMPtr hc = do_QueryInterface(mChannel); michael@0: if (hc) { michael@0: // Use |mByteRange| for a specific chunk, or |mOffset| if seeking in a michael@0: // complete file download. michael@0: nsAutoCString rangeString("bytes="); michael@0: if (!mByteRange.IsNull()) { michael@0: rangeString.AppendInt(mByteRange.mStart); michael@0: mOffset = mByteRange.mStart; michael@0: } else { michael@0: rangeString.AppendInt(mOffset); michael@0: } michael@0: rangeString.Append("-"); michael@0: if (!mByteRange.IsNull()) { michael@0: rangeString.AppendInt(mByteRange.mEnd); michael@0: } michael@0: hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false); michael@0: michael@0: // Send Accept header for video and audio types only (Bug 489071) michael@0: NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: if (!owner) { michael@0: return; michael@0: } michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: if (!element) { michael@0: return; michael@0: } michael@0: element->SetRequestHeaders(hc); michael@0: } else { michael@0: NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type"); michael@0: } michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::Close() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: mCacheStream.Close(); michael@0: CloseChannel(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed ChannelMediaResource::GetCurrentPrincipal() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: nsCOMPtr principal = mCacheStream.GetCurrentPrincipal(); michael@0: return principal.forget(); michael@0: } michael@0: michael@0: bool ChannelMediaResource::CanClone() michael@0: { michael@0: return mCacheStream.IsAvailableForSharing(); michael@0: } michael@0: michael@0: already_AddRefed ChannelMediaResource::CloneData(MediaDecoder* aDecoder) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned"); michael@0: michael@0: nsRefPtr resource = michael@0: new ChannelMediaResource(aDecoder, michael@0: nullptr, michael@0: mURI, michael@0: GetContentType()); michael@0: if (resource) { michael@0: // Initially the clone is treated as suspended by the cache, because michael@0: // we don't have a channel. If the cache needs to read data from the clone michael@0: // it will call CacheClientResume (or CacheClientSeek with aResume true) michael@0: // which will recreate the channel. This way, if all of the media data michael@0: // is already in the cache we don't create an unnecessary HTTP channel michael@0: // and perform a useless HTTP transaction. michael@0: resource->mSuspendCount = 1; michael@0: resource->mCacheStream.InitAsClone(&mCacheStream); michael@0: resource->mChannelStatistics = new MediaChannelStatistics(mChannelStatistics); michael@0: resource->mChannelStatistics->Stop(); michael@0: } michael@0: return resource.forget(); michael@0: } michael@0: michael@0: void ChannelMediaResource::CloseChannel() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mChannelStatistics->Stop(); michael@0: } michael@0: michael@0: if (mListener) { michael@0: mListener->Revoke(); michael@0: mListener = nullptr; michael@0: } michael@0: michael@0: if (mChannel) { michael@0: if (mSuspendCount > 0) { michael@0: // Resume the channel before we cancel it michael@0: PossiblyResume(); michael@0: } michael@0: // The status we use here won't be passed to the decoder, since michael@0: // we've already revoked the listener. It can however be passed michael@0: // to nsDocumentViewer::LoadComplete if our channel is the one michael@0: // that kicked off creation of a video document. We don't want that michael@0: // document load to think there was an error. michael@0: // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that michael@0: // at the moment. michael@0: mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED); michael@0: mChannel = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::ReadFromCache(char* aBuffer, michael@0: int64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount); michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::Read(char* aBuffer, michael@0: uint32_t aCount, michael@0: uint32_t* aBytes) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: int64_t offset = mCacheStream.Tell(); michael@0: nsresult rv = mCacheStream.Read(aBuffer, aCount, aBytes); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: DispatchBytesConsumed(*aBytes, offset); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::ReadAt(int64_t aOffset, michael@0: char* aBuffer, michael@0: uint32_t aCount, michael@0: uint32_t* aBytes) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: nsresult rv = mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: DispatchBytesConsumed(*aBytes, aOffset); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: CMLOG("Seek requested for aOffset [%lld] for decoder [%p]", michael@0: aOffset, mDecoder); michael@0: return mCacheStream.Seek(aWhence, aOffset); michael@0: } michael@0: michael@0: void ChannelMediaResource::StartSeekingForMetadata() michael@0: { michael@0: mSeekingForMetadata = true; michael@0: } michael@0: michael@0: void ChannelMediaResource::EndSeekingForMetadata() michael@0: { michael@0: mSeekingForMetadata = false; michael@0: } michael@0: michael@0: int64_t ChannelMediaResource::Tell() michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: return mCacheStream.Tell(); michael@0: } michael@0: michael@0: nsresult ChannelMediaResource::GetCachedRanges(nsTArray& aRanges) michael@0: { michael@0: return mCacheStream.GetCachedRanges(aRanges); michael@0: } michael@0: michael@0: void ChannelMediaResource::Suspend(bool aCloseImmediately) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); michael@0: michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: if (!owner) { michael@0: // Shutting down; do nothing. michael@0: return; michael@0: } michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: if (!element) { michael@0: // Shutting down; do nothing. michael@0: return; michael@0: } michael@0: michael@0: if (mChannel) { michael@0: if (aCloseImmediately && mCacheStream.IsTransportSeekable()) { michael@0: // Kill off our channel right now, but don't tell anyone about it. michael@0: mIgnoreClose = true; michael@0: CloseChannel(); michael@0: element->DownloadSuspended(); michael@0: } else if (mSuspendCount == 0) { michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mChannelStatistics->Stop(); michael@0: } michael@0: PossiblySuspend(); michael@0: element->DownloadSuspended(); michael@0: } michael@0: } michael@0: michael@0: ++mSuspendCount; michael@0: } michael@0: michael@0: void ChannelMediaResource::Resume() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); michael@0: NS_ASSERTION(mSuspendCount > 0, "Too many resumes!"); michael@0: michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: if (!owner) { michael@0: // Shutting down; do nothing. michael@0: return; michael@0: } michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: if (!element) { michael@0: // Shutting down; do nothing. michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!"); michael@0: --mSuspendCount; michael@0: if (mSuspendCount == 0) { michael@0: if (mChannel) { michael@0: // Just wake up our existing channel michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mChannelStatistics->Start(); michael@0: } michael@0: // if an error occurs after Resume, assume it's because the server michael@0: // timed out the connection and we should reopen it. michael@0: mReopenOnError = true; michael@0: PossiblyResume(); michael@0: element->DownloadResumed(); michael@0: } else { michael@0: int64_t totalLength = mCacheStream.GetLength(); michael@0: // If mOffset is at the end of the stream, then we shouldn't try to michael@0: // seek to it. The seek will fail and be wasted anyway. We can leave michael@0: // the channel dead; if the media cache wants to read some other data michael@0: // in the future, it will call CacheClientSeek itself which will reopen the michael@0: // channel. michael@0: if (totalLength < 0 || mOffset < totalLength) { michael@0: // There is (or may be) data to read at mOffset, so start reading it. michael@0: // Need to recreate the channel. michael@0: CacheClientSeek(mOffset, false); michael@0: } michael@0: element->DownloadResumed(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::RecreateChannel() michael@0: { michael@0: nsLoadFlags loadFlags = michael@0: nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY | michael@0: (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0); michael@0: michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: if (!owner) { michael@0: // The decoder is being shut down, so don't bother opening a new channel michael@0: return NS_OK; michael@0: } michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: if (!element) { michael@0: // The decoder is being shut down, so don't bother opening a new channel michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr loadGroup = element->GetDocumentLoadGroup(); michael@0: NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), michael@0: mURI, michael@0: nullptr, michael@0: loadGroup, michael@0: nullptr, michael@0: loadFlags); michael@0: michael@0: // We have cached the Content-Type, which should not change. Give a hint to michael@0: // the channel to avoid a sniffing failure, which would be expected because we michael@0: // are probably seeking in the middle of the bitstream, and sniffing relies michael@0: // on the presence of a magic number at the beginning of the stream. michael@0: NS_ASSERTION(!GetContentType().IsEmpty(), michael@0: "When recreating a channel, we should know the Content-Type."); michael@0: mChannel->SetContentType(GetContentType()); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::DoNotifyDataReceived() michael@0: { michael@0: mDataReceivedEvent.Revoke(); michael@0: mDecoder->NotifyBytesDownloaded(); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::CacheClientNotifyDataReceived() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); michael@0: // NOTE: this can be called with the media cache lock held, so don't michael@0: // block or do anything which might try to acquire a lock! michael@0: michael@0: if (mDataReceivedEvent.IsPending()) michael@0: return; michael@0: michael@0: mDataReceivedEvent = michael@0: NS_NewNonOwningRunnableMethod(this, &ChannelMediaResource::DoNotifyDataReceived); michael@0: NS_DispatchToMainThread(mDataReceivedEvent.get(), NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: class DataEnded : public nsRunnable { michael@0: public: michael@0: DataEnded(MediaDecoder* aDecoder, nsresult aStatus) : michael@0: mDecoder(aDecoder), mStatus(aStatus) {} michael@0: NS_IMETHOD Run() { michael@0: mDecoder->NotifyDownloadEnded(mStatus); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mDecoder; michael@0: nsresult mStatus; michael@0: }; michael@0: michael@0: void michael@0: ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); michael@0: // NOTE: this can be called with the media cache lock held, so don't michael@0: // block or do anything which might try to acquire a lock! michael@0: michael@0: nsCOMPtr event = new DataEnded(mDecoder, aStatus); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::CacheClientNotifyPrincipalChanged() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); michael@0: michael@0: mDecoder->NotifyPrincipalChanged(); michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); michael@0: michael@0: CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]", michael@0: aOffset, mDecoder); michael@0: michael@0: CloseChannel(); michael@0: michael@0: if (aResume) { michael@0: NS_ASSERTION(mSuspendCount > 0, "Too many resumes!"); michael@0: // No need to mess with the channel, since we're making a new one michael@0: --mSuspendCount; michael@0: } michael@0: michael@0: mOffset = aOffset; michael@0: michael@0: if (mSuspendCount > 0) { michael@0: // Close the existing channel to force the channel to be recreated at michael@0: // the correct offset upon resume. michael@0: if (mChannel) { michael@0: mIgnoreClose = true; michael@0: CloseChannel(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = RecreateChannel(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return OpenChannel(nullptr); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::FlushCache() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); michael@0: michael@0: // Ensure that data in the cache's partial block is written to disk. michael@0: mCacheStream.FlushPartialBlock(); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::NotifyLastByteRange() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); michael@0: michael@0: // Tell media cache that the last data has been downloaded. michael@0: // Note: subsequent seeks will require re-opening the channel etc. michael@0: mCacheStream.NotifyDataEnded(NS_OK); michael@0: michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::CacheClientSuspend() michael@0: { michael@0: Suspend(false); michael@0: michael@0: mDecoder->NotifySuspendedStatusChanged(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ChannelMediaResource::CacheClientResume() michael@0: { michael@0: Resume(); michael@0: michael@0: mDecoder->NotifySuspendedStatusChanged(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: int64_t michael@0: ChannelMediaResource::GetNextCachedData(int64_t aOffset) michael@0: { michael@0: return mCacheStream.GetNextCachedData(aOffset); michael@0: } michael@0: michael@0: int64_t michael@0: ChannelMediaResource::GetCachedDataEnd(int64_t aOffset) michael@0: { michael@0: return mCacheStream.GetCachedDataEnd(aOffset); michael@0: } michael@0: michael@0: bool michael@0: ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset) michael@0: { michael@0: return mCacheStream.IsDataCachedToEndOfStream(aOffset); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::EnsureCacheUpToDate() michael@0: { michael@0: mCacheStream.EnsureCacheUpdate(); michael@0: } michael@0: michael@0: bool michael@0: ChannelMediaResource::IsSuspendedByCache() michael@0: { michael@0: return mCacheStream.AreAllStreamsForResourceSuspended(); michael@0: } michael@0: michael@0: bool michael@0: ChannelMediaResource::IsSuspended() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: return mSuspendCount > 0; michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode) michael@0: { michael@0: mCacheStream.SetReadMode(aMode); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond) michael@0: { michael@0: mCacheStream.SetPlaybackRate(aBytesPerSecond); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::Pin() michael@0: { michael@0: mCacheStream.Pin(); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::Unpin() michael@0: { michael@0: mCacheStream.Unpin(); michael@0: } michael@0: michael@0: double michael@0: ChannelMediaResource::GetDownloadRate(bool* aIsReliable) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: return mChannelStatistics->GetRate(aIsReliable); michael@0: } michael@0: michael@0: int64_t michael@0: ChannelMediaResource::GetLength() michael@0: { michael@0: return mCacheStream.GetLength(); michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::PossiblySuspend() michael@0: { michael@0: bool isPending = false; michael@0: nsresult rv = mChannel->IsPending(&isPending); michael@0: if (NS_SUCCEEDED(rv) && isPending) { michael@0: mChannel->Suspend(); michael@0: mIgnoreResume = false; michael@0: } else { michael@0: mIgnoreResume = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ChannelMediaResource::PossiblyResume() michael@0: { michael@0: if (!mIgnoreResume) { michael@0: mChannel->Resume(); michael@0: } else { michael@0: mIgnoreResume = false; michael@0: } michael@0: } michael@0: michael@0: class FileMediaResource : public BaseMediaResource michael@0: { michael@0: public: michael@0: FileMediaResource(MediaDecoder* aDecoder, michael@0: nsIChannel* aChannel, michael@0: nsIURI* aURI, michael@0: const nsACString& aContentType) : michael@0: BaseMediaResource(aDecoder, aChannel, aURI, aContentType), michael@0: mSize(-1), michael@0: mLock("FileMediaResource.mLock"), michael@0: mSizeInitialized(false) michael@0: { michael@0: } michael@0: ~FileMediaResource() michael@0: { michael@0: } michael@0: michael@0: // Main thread michael@0: virtual nsresult Open(nsIStreamListener** aStreamListener); michael@0: virtual nsresult Close(); michael@0: virtual void Suspend(bool aCloseImmediately) {} michael@0: virtual void Resume() {} michael@0: virtual already_AddRefed GetCurrentPrincipal(); michael@0: virtual bool CanClone(); michael@0: virtual already_AddRefed CloneData(MediaDecoder* aDecoder); michael@0: virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount); michael@0: michael@0: // These methods are called off the main thread. michael@0: michael@0: // Other thread michael@0: virtual void SetReadMode(MediaCacheStream::ReadMode aMode) {} michael@0: virtual void SetPlaybackRate(uint32_t aBytesPerSecond) {} michael@0: virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes); michael@0: virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, michael@0: uint32_t aCount, uint32_t* aBytes); michael@0: virtual nsresult Seek(int32_t aWhence, int64_t aOffset); michael@0: virtual void StartSeekingForMetadata() {}; michael@0: virtual void EndSeekingForMetadata() {}; michael@0: virtual int64_t Tell(); michael@0: michael@0: // Any thread michael@0: virtual void Pin() {} michael@0: virtual void Unpin() {} michael@0: virtual double GetDownloadRate(bool* aIsReliable) michael@0: { michael@0: // The data's all already here michael@0: *aIsReliable = true; michael@0: return 100*1024*1024; // arbitray, use 100MB/s michael@0: } michael@0: virtual int64_t GetLength() { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: EnsureSizeInitialized(); michael@0: return mSizeInitialized ? mSize : 0; michael@0: } michael@0: virtual int64_t GetNextCachedData(int64_t aOffset) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: EnsureSizeInitialized(); michael@0: return (aOffset < mSize) ? aOffset : -1; michael@0: } michael@0: virtual int64_t GetCachedDataEnd(int64_t aOffset) { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: EnsureSizeInitialized(); michael@0: return std::max(aOffset, mSize); michael@0: } michael@0: virtual bool IsDataCachedToEndOfResource(int64_t aOffset) { return true; } michael@0: virtual bool IsSuspendedByCache() { return false; } michael@0: virtual bool IsSuspended() { return false; } michael@0: virtual bool IsTransportSeekable() MOZ_OVERRIDE { return true; } michael@0: michael@0: nsresult GetCachedRanges(nsTArray& aRanges); michael@0: michael@0: virtual size_t SizeOfExcludingThis( michael@0: MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: // Might be useful to track in the future: michael@0: // - mInput michael@0: return BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis( michael@0: MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: protected: michael@0: // These Unsafe variants of Read and Seek perform their operations michael@0: // without acquiring mLock. The caller must obtain the lock before michael@0: // calling. The implmentation of Read, Seek and ReadAt obtains the michael@0: // lock before calling these Unsafe variants to read or seek. michael@0: nsresult UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes); michael@0: nsresult UnsafeSeek(int32_t aWhence, int64_t aOffset); michael@0: private: michael@0: // Ensures mSize is initialized, if it can be. michael@0: // mLock must be held when this is called, and mInput must be non-null. michael@0: void EnsureSizeInitialized(); michael@0: michael@0: // The file size, or -1 if not known. Immutable after Open(). michael@0: // Can be used from any thread. michael@0: int64_t mSize; michael@0: michael@0: // This lock handles synchronisation between calls to Close() and michael@0: // the Read, Seek, etc calls. Close must not be called while a michael@0: // Read or Seek is in progress since it resets various internal michael@0: // values to null. michael@0: // This lock protects mSeekable, mInput, mSize, and mSizeInitialized. michael@0: Mutex mLock; michael@0: michael@0: // Seekable stream interface to file. This can be used from any michael@0: // thread. michael@0: nsCOMPtr mSeekable; michael@0: michael@0: // Input stream for the media data. This can be used from any michael@0: // thread. michael@0: nsCOMPtr mInput; michael@0: michael@0: // Whether we've attempted to initialize mSize. Note that mSize can be -1 michael@0: // when mSizeInitialized is true if we tried and failed to get the size michael@0: // of the file. michael@0: bool mSizeInitialized; michael@0: }; michael@0: michael@0: void FileMediaResource::EnsureSizeInitialized() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: NS_ASSERTION(mInput, "Must have file input stream"); michael@0: if (mSizeInitialized) { michael@0: return; michael@0: } michael@0: mSizeInitialized = true; michael@0: // Get the file size and inform the decoder. michael@0: uint64_t size; michael@0: nsresult res = mInput->Available(&size); michael@0: if (NS_SUCCEEDED(res) && size <= INT64_MAX) { michael@0: mSize = (int64_t)size; michael@0: nsCOMPtr event = new DataEnded(mDecoder, NS_OK); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: nsresult FileMediaResource::GetCachedRanges(nsTArray& aRanges) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: EnsureSizeInitialized(); michael@0: if (mSize == -1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: aRanges.AppendElement(MediaByteRange(0, mSize)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: if (aStreamListener) { michael@0: *aStreamListener = nullptr; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (aStreamListener) { michael@0: // The channel is already open. We need a synchronous stream that michael@0: // implements nsISeekableStream, so we have to find the underlying michael@0: // file and reopen it michael@0: nsCOMPtr fc(do_QueryInterface(mChannel)); michael@0: if (fc) { michael@0: nsCOMPtr file; michael@0: rv = fc->GetFile(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file); michael@0: } else if (IsBlobURI(mURI)) { michael@0: rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput)); michael@0: } michael@0: } else { michael@0: // Ensure that we never load a local file from some page on a michael@0: // web server. michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); michael@0: michael@0: rv = nsContentUtils::GetSecurityManager()-> michael@0: CheckLoadURIWithPrincipal(element->NodePrincipal(), michael@0: mURI, michael@0: nsIScriptSecurityManager::STANDARD); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mChannel->Open(getter_AddRefs(mInput)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mSeekable = do_QueryInterface(mInput); michael@0: if (!mSeekable) { michael@0: // XXX The file may just be a .url or similar michael@0: // shortcut that points to a Web site. We need to fix this by michael@0: // doing an async open and waiting until we locate the real resource, michael@0: // then using that (if it's still a file!). michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult FileMediaResource::Close() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: // Since mChennel is only accessed by main thread, there is no necessary to michael@0: // take the lock. michael@0: if (mChannel) { michael@0: mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED); michael@0: mChannel = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed FileMediaResource::GetCurrentPrincipal() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: nsCOMPtr principal; michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: if (!secMan || !mChannel) michael@0: return nullptr; michael@0: secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal)); michael@0: return principal.forget(); michael@0: } michael@0: michael@0: bool FileMediaResource::CanClone() michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: already_AddRefed FileMediaResource::CloneData(MediaDecoder* aDecoder) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: if (!owner) { michael@0: // The decoder is being shut down, so we can't clone michael@0: return nullptr; michael@0: } michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: if (!element) { michael@0: // The decoder is being shut down, so we can't clone michael@0: return nullptr; michael@0: } michael@0: nsCOMPtr loadGroup = element->GetDocumentLoadGroup(); michael@0: NS_ENSURE_TRUE(loadGroup, nullptr); michael@0: michael@0: nsCOMPtr channel; michael@0: nsresult rv = michael@0: NS_NewChannel(getter_AddRefs(channel), mURI, nullptr, loadGroup, nullptr, 0); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: michael@0: nsRefPtr resource(new FileMediaResource(aDecoder, channel, mURI, GetContentType())); michael@0: return resource.forget(); michael@0: } michael@0: michael@0: nsresult FileMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: EnsureSizeInitialized(); michael@0: int64_t offset = 0; michael@0: nsresult res = mSeekable->Tell(&offset); michael@0: NS_ENSURE_SUCCESS(res,res); michael@0: res = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); michael@0: NS_ENSURE_SUCCESS(res,res); michael@0: uint32_t bytesRead = 0; michael@0: do { michael@0: uint32_t x = 0; michael@0: uint32_t bytesToRead = aCount - bytesRead; michael@0: res = mInput->Read(aBuffer, bytesToRead, &x); michael@0: bytesRead += x; michael@0: } while (bytesRead != aCount && res == NS_OK); michael@0: michael@0: // Reset read head to original position so we don't disturb any other michael@0: // reading thread. michael@0: nsresult seekres = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: michael@0: // If a read failed in the loop above, we want to return its failure code. michael@0: NS_ENSURE_SUCCESS(res,res); michael@0: michael@0: // Else we succeed if the reset-seek succeeds. michael@0: return seekres; michael@0: } michael@0: michael@0: nsresult FileMediaResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) michael@0: { michael@0: nsresult rv; michael@0: int64_t offset = 0; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mSeekable->Tell(&offset); michael@0: rv = UnsafeRead(aBuffer, aCount, aBytes); michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: DispatchBytesConsumed(*aBytes, offset); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes) michael@0: { michael@0: EnsureSizeInitialized(); michael@0: return mInput->Read(aBuffer, aCount, aBytes); michael@0: } michael@0: michael@0: nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer, michael@0: uint32_t aCount, uint32_t* aBytes) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: nsresult rv; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = UnsafeRead(aBuffer, aCount, aBytes); michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: DispatchBytesConsumed(*aBytes, aOffset); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult FileMediaResource::Seek(int32_t aWhence, int64_t aOffset) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: return UnsafeSeek(aWhence, aOffset); michael@0: } michael@0: michael@0: nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: if (!mSeekable) michael@0: return NS_ERROR_FAILURE; michael@0: EnsureSizeInitialized(); michael@0: return mSeekable->Seek(aWhence, aOffset); michael@0: } michael@0: michael@0: int64_t FileMediaResource::Tell() michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: if (!mSeekable) michael@0: return 0; michael@0: EnsureSizeInitialized(); michael@0: michael@0: int64_t offset = 0; michael@0: mSeekable->Tell(&offset); michael@0: return offset; michael@0: } michael@0: michael@0: already_AddRefed michael@0: MediaResource::Create(MediaDecoder* aDecoder, nsIChannel* aChannel) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), michael@0: "MediaResource::Open called on non-main thread"); michael@0: michael@0: // If the channel was redirected, we want the post-redirect URI; michael@0: // but if the URI scheme was expanded, say from chrome: to jar:file:, michael@0: // we want the original URI. michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: nsAutoCString contentType; michael@0: aChannel->GetContentType(contentType); michael@0: michael@0: nsCOMPtr fc = do_QueryInterface(aChannel); michael@0: nsRefPtr resource; michael@0: if (fc || IsBlobURI(uri)) { michael@0: resource = new FileMediaResource(aDecoder, aChannel, uri, contentType); michael@0: } else if (IsRtspURI(uri)) { michael@0: resource = new RtspMediaResource(aDecoder, aChannel, uri, contentType); michael@0: } else { michael@0: resource = new ChannelMediaResource(aDecoder, aChannel, uri, contentType); michael@0: } michael@0: return resource.forget(); michael@0: } michael@0: michael@0: void BaseMediaResource::MoveLoadsToBackground() { michael@0: NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?"); michael@0: mLoadInBackground = true; michael@0: if (!mChannel) { michael@0: // No channel, resource is probably already loaded. michael@0: return; michael@0: } michael@0: michael@0: MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); michael@0: if (!owner) { michael@0: NS_WARNING("Null owner in MediaResource::MoveLoadsToBackground()"); michael@0: return; michael@0: } michael@0: dom::HTMLMediaElement* element = owner->GetMediaElement(); michael@0: if (!element) { michael@0: NS_WARNING("Null element in MediaResource::MoveLoadsToBackground()"); michael@0: return; michael@0: } michael@0: michael@0: bool isPending = false; michael@0: if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) && michael@0: isPending) { michael@0: nsLoadFlags loadFlags; michael@0: DebugOnly rv = mChannel->GetLoadFlags(&loadFlags); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!"); michael@0: michael@0: loadFlags |= nsIRequest::LOAD_BACKGROUND; michael@0: ModifyLoadFlags(loadFlags); michael@0: } michael@0: } michael@0: michael@0: void BaseMediaResource::ModifyLoadFlags(nsLoadFlags aFlags) michael@0: { michael@0: nsCOMPtr loadGroup; michael@0: DebugOnly rv = mChannel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadGroup() failed!"); michael@0: michael@0: nsresult status; michael@0: mChannel->GetStatus(&status); michael@0: michael@0: // Note: if (NS_FAILED(status)), the channel won't be in the load group. michael@0: if (loadGroup && michael@0: NS_SUCCEEDED(status)) { michael@0: rv = loadGroup->RemoveRequest(mChannel, nullptr, status); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveRequest() failed!"); michael@0: } michael@0: michael@0: rv = mChannel->SetLoadFlags(aFlags); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "SetLoadFlags() failed!"); michael@0: michael@0: if (loadGroup && michael@0: NS_SUCCEEDED(status)) { michael@0: rv = loadGroup->AddRequest(mChannel, nullptr); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "AddRequest() failed!"); michael@0: } michael@0: } michael@0: michael@0: class DispatchBytesConsumedEvent : public nsRunnable { michael@0: public: michael@0: DispatchBytesConsumedEvent(MediaDecoder* aDecoder, michael@0: int64_t aNumBytes, michael@0: int64_t aOffset) michael@0: : mDecoder(aDecoder), michael@0: mNumBytes(aNumBytes), michael@0: mOffset(aOffset) michael@0: { michael@0: MOZ_COUNT_CTOR(DispatchBytesConsumedEvent); michael@0: } michael@0: michael@0: ~DispatchBytesConsumedEvent() michael@0: { michael@0: MOZ_COUNT_DTOR(DispatchBytesConsumedEvent); michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: mDecoder->NotifyBytesConsumed(mNumBytes, mOffset); michael@0: // Drop ref to decoder on main thread, just in case this reference michael@0: // ends up being the last owning reference somehow. michael@0: mDecoder = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: RefPtr mDecoder; michael@0: int64_t mNumBytes; michael@0: int64_t mOffset; michael@0: }; michael@0: michael@0: void BaseMediaResource::DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset) michael@0: { michael@0: if (aNumBytes <= 0) { michael@0: return; michael@0: } michael@0: RefPtr event(new DispatchBytesConsumedEvent(mDecoder, aNumBytes, aOffset)); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: } // namespace mozilla michael@0: