void ChannelMediaResource::Suspend(bool aCloseImmediately) { NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); MediaDecoderOwner* owner = mCallback->GetMediaOwner(); if (!owner) { // Shutting down; do nothing. return; } dom::HTMLMediaElement* element = owner->GetMediaElement(); if (!element) { // Shutting down; do nothing. return; } if (mChannel && aCloseImmediately && mCacheStream.IsTransportSeekable()) { // Kill off our channel right now, but don't tell anyone about it. mIgnoreClose = true; CloseChannel(); element->DownloadSuspended(); } if (mSuspendAgent.Suspend()) { if (mChannel) { { MutexAutoLock lock(mLock); mChannelStatistics->Stop(); } element->DownloadSuspended(); } } }
nsresult ChannelMediaResource::SetupChannelHeaders() { // Always use a byte range request even if we're reading from the start // of the resource. // This enables us to detect if the stream supports byte range // requests, and therefore seeking, early. nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel); if (hc) { // Use |mOffset| if seeking in a complete file download. nsAutoCString rangeString("bytes="); rangeString.AppendInt(mOffset); rangeString.Append('-'); nsresult rv = hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false); NS_ENSURE_SUCCESS(rv, rv); // Send Accept header for video and audio types only (Bug 489071) NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); MediaDecoderOwner* owner = mCallback->GetMediaOwner(); NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); element->SetRequestHeaders(hc); } else { NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type"); return NS_ERROR_FAILURE; } return NS_OK; }
bool WMFReader::InitializeDXVA() { if (!gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding()) { return false; } MOZ_ASSERT(mDecoder->GetImageContainer()); // Extract the layer manager backend type so that we can determine // whether it's worthwhile using DXVA. If we're not running with a D3D // layer manager then the readback of decoded video frames from GPU to // CPU memory grinds painting to a halt, and makes playback performance // *worse*. MediaDecoderOwner* owner = mDecoder->GetOwner(); NS_ENSURE_TRUE(owner, false); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE(element, false); nsRefPtr<LayerManager> layerManager = nsContentUtils::LayerManagerForDocument(element->OwnerDoc()); NS_ENSURE_TRUE(layerManager, false); LayersBackend backend = layerManager->GetCompositorBackendType(); if (backend != LayersBackend::LAYERS_D3D9 && backend != LayersBackend::LAYERS_D3D10 && backend != LayersBackend::LAYERS_D3D11) { return false; } mDXVA2Manager = DXVA2Manager::CreateD3D9DXVA(); return mDXVA2Manager != nullptr; }
void MP4Reader::InitLayersBackendType() { if (!IsVideoContentType(mDecoder->GetResource()->GetContentType())) { // Not playing video, we don't care about the layers backend type. return; } // Extract the layer manager backend type so that platform decoders // can determine whether it's worthwhile using hardware accelerated // video decoding. MediaDecoderOwner* owner = mDecoder->GetOwner(); if (!owner) { NS_WARNING("MP4Reader without a decoder owner, can't get HWAccel"); return; } dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE_VOID(element); nsRefPtr<LayerManager> layerManager = nsContentUtils::LayerManagerForDocument(element->OwnerDoc()); NS_ENSURE_TRUE_VOID(layerManager); mLayersBackendType = layerManager->GetCompositorBackendType(); }
bool WMFReader::InitializeDXVA() { if (!Preferences::GetBool("media.windows-media-foundation.use-dxva", false)) { return false; } MOZ_ASSERT(mDecoder->GetImageContainer()); // Extract the layer manager backend type so that we can determine // whether it's worthwhile using DXVA. If we're not running with a D3D // layer manager then the readback of decoded video frames from GPU to // CPU memory grinds painting to a halt, and makes playback performance // *worse*. MediaDecoderOwner* owner = mDecoder->GetOwner(); NS_ENSURE_TRUE(owner, false); HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE(element, false); nsRefPtr<LayerManager> layerManager = nsContentUtils::LayerManagerForDocument(element->OwnerDoc()); NS_ENSURE_TRUE(layerManager, false); if (layerManager->GetBackendType() != LayersBackend::LAYERS_D3D9 && layerManager->GetBackendType() != LayersBackend::LAYERS_D3D10) { return false; } mDXVA2Manager = DXVA2Manager::Create(); return mDXVA2Manager != nullptr; }
nsresult ChannelMediaResource::RecreateChannel() { nsLoadFlags loadFlags = nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY | nsIChannel::LOAD_CLASSIFY_URI | (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0); MediaDecoderOwner* owner = mCallback->GetMediaOwner(); if (!owner) { // The decoder is being shut down, so don't bother opening a new channel return NS_OK; } dom::HTMLMediaElement* element = owner->GetMediaElement(); if (!element) { // The decoder is being shut down, so don't bother opening a new channel return NS_OK; } nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup(); NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER); nsSecurityFlags securityFlags = element->ShouldCheckAllowOrigin() ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)); nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ? nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO; nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), mURI, element, securityFlags, contentPolicyType, loadGroup, nullptr, // aCallbacks loadFlags); NS_ENSURE_SUCCESS(rv, rv); // We have cached the Content-Type, which should not change. Give a hint to // the channel to avoid a sniffing failure, which would be expected because we // are probably seeking in the middle of the bitstream, and sniffing relies // on the presence of a magic number at the beginning of the stream. NS_ASSERTION(!GetContentType().IsEmpty(), "When recreating a channel, we should know the Content-Type."); mChannel->SetContentType(GetContentType()); mSuspendAgent.NotifyChannelOpened(mChannel); // Tell the cache to reset the download status when the channel is reopened. mCacheStream.NotifyChannelRecreated(); return rv; }
void RtspMediaResource::Suspend(bool aCloseImmediately) { NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); NS_ENSURE_TRUE_VOID(owner); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE_VOID(element); mMediaStreamController->Suspend(); element->DownloadSuspended(); }
void RtspMediaResource::Resume() { NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); NS_ENSURE_TRUE_VOID(owner); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE_VOID(element); if (mChannel) { element->DownloadResumed(); } mMediaStreamController->Resume(); }
void ChannelMediaResource::Resume() { NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); NS_ASSERTION(mSuspendCount > 0, "Too many resumes!"); MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); if (!owner) { // Shutting down; do nothing. return; } dom::HTMLMediaElement* element = owner->GetMediaElement(); if (!element) { // Shutting down; do nothing. return; } NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!"); --mSuspendCount; if (mSuspendCount == 0) { if (mChannel) { // Just wake up our existing channel { MutexAutoLock lock(mLock); mChannelStatistics->Start(); } // if an error occurs after Resume, assume it's because the server // timed out the connection and we should reopen it. mReopenOnError = true; PossiblyResume(); element->DownloadResumed(); } else { int64_t totalLength = mCacheStream.GetLength(); // If mOffset is at the end of the stream, then we shouldn't try to // seek to it. The seek will fail and be wasted anyway. We can leave // the channel dead; if the media cache wants to read some other data // in the future, it will call CacheClientSeek itself which will reopen the // channel. if (totalLength < 0 || mOffset < totalLength) { // There is (or may be) data to read at mOffset, so start reading it. // Need to recreate the channel. CacheClientSeek(mOffset, false); } element->DownloadResumed(); } } }
nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener) { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER); NS_ASSERTION(!mListener, "Listener should have been removed by now"); if (aStreamListener) { *aStreamListener = nullptr; } // Set the content length, if it's available as an HTTP header. // This ensures that MediaResource wrapping objects for platform libraries // that expect to know the length of a resource can get it before // OnStartRequest() fires. nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel); if (hc) { int64_t cl = -1; if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) { mCacheStream.NotifyDataLength(cl); } } mListener = new Listener(this); if (aStreamListener) { *aStreamListener = mListener; NS_ADDREF(*aStreamListener); } else { nsresult rv = mChannel->SetNotificationCallbacks(mListener.get()); NS_ENSURE_SUCCESS(rv, rv); rv = SetupChannelHeaders(); NS_ENSURE_SUCCESS(rv, rv); rv = mChannel->AsyncOpen2(mListener); NS_ENSURE_SUCCESS(rv, rv); // Tell the media element that we are fetching data from a channel. MediaDecoderOwner* owner = mCallback->GetMediaOwner(); NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); dom::HTMLMediaElement* element = owner->GetMediaElement(); element->DownloadResumed(true); } return NS_OK; }
void RtspMediaResource::Suspend(bool aCloseImmediately) { NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); mIsSuspend = true; if (NS_WARN_IF(!mDecoder)) { return; } MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); NS_ENSURE_TRUE_VOID(owner); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE_VOID(element); mMediaStreamController->Suspend(); element->DownloadSuspended(); mDecoder->NotifySuspendedStatusChanged(); }
void MediaDecoder::NotifyOwnerActivityChanged(bool aIsVisible) { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(!IsShutdown()); SetElementVisibility(aIsVisible); MediaDecoderOwner* owner = GetOwner(); NS_ENSURE_TRUE_VOID(owner); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE_VOID(element); RefPtr<LayerManager> layerManager = nsContentUtils::LayerManagerForDocument(element->OwnerDoc()); NS_ENSURE_TRUE_VOID(layerManager); RefPtr<KnowsCompositor> knowsCompositor = layerManager->AsShadowForwarder(); mCompositorUpdatedEvent.Notify(knowsCompositor); }
void RtspMediaResource::Resume() { NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); mIsSuspend = false; if (NS_WARN_IF(!mDecoder)) { return; } MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); NS_ENSURE_TRUE_VOID(owner); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE_VOID(element); if (mChannel) { element->DownloadResumed(); } mMediaStreamController->Resume(); mDecoder->NotifySuspendedStatusChanged(); }
already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaResourceCallback* aCallback) { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); MediaDecoderOwner* owner = mCallback->GetMediaOwner(); if (!owner) { // The decoder is being shut down, so we can't clone return nullptr; } dom::HTMLMediaElement* element = owner->GetMediaElement(); if (!element) { // The decoder is being shut down, so we can't clone return nullptr; } nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup(); NS_ENSURE_TRUE(loadGroup, nullptr); MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)); nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ? nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO; nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI; nsCOMPtr<nsIChannel> channel; nsresult rv = NS_NewChannel(getter_AddRefs(channel), mURI, element, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS, contentPolicyType, loadGroup, nullptr, // aCallbacks loadFlags); if (NS_FAILED(rv)) return nullptr; RefPtr<MediaResource> resource(new FileMediaResource(aCallback, channel, mURI, GetContentType())); return resource.forget(); }
nsresult ChannelMediaResource::RecreateChannel() { nsLoadFlags loadFlags = nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY | (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0); MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); if (!owner) { // The decoder is being shut down, so don't bother opening a new channel return NS_OK; } dom::HTMLMediaElement* element = owner->GetMediaElement(); if (!element) { // The decoder is being shut down, so don't bother opening a new channel return NS_OK; } nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup(); NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER); nsCOMPtr<nsIChannel> channel; nsresult rv = NS_NewChannel(getter_AddRefs(channel), mURI, nullptr, loadGroup, nullptr, loadFlags); mChannel = new nsMainThreadPtrHolder<nsIChannel>(channel); // We have cached the Content-Type, which should not change. Give a hint to // the channel to avoid a sniffing failure, which would be expected because we // are probably seeking in the middle of the bitstream, and sniffing relies // on the presence of a magic number at the beginning of the stream. NS_ASSERTION(!GetContentType().IsEmpty(), "When recreating a channel, we should know the Content-Type."); mChannel->SetContentType(GetContentType()); return rv; }
void ChannelMediaResource::SetupChannelHeaders() { // Always use a byte range request even if we're reading from the start // of the resource. // This enables us to detect if the stream supports byte range // requests, and therefore seeking, early. nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel); if (hc) { // Use |mByteRange| for a specific chunk, or |mOffset| if seeking in a // complete file download. nsAutoCString rangeString("bytes="); if (!mByteRange.IsNull()) { rangeString.AppendInt(mByteRange.mStart); mOffset = mByteRange.mStart; } else { rangeString.AppendInt(mOffset); } rangeString.Append("-"); if (!mByteRange.IsNull()) { rangeString.AppendInt(mByteRange.mEnd); } hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false); // Send Accept header for video and audio types only (Bug 489071) NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread"); MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); if (!owner) { return; } dom::HTMLMediaElement* element = owner->GetMediaElement(); if (!element) { return; } element->SetRequestHeaders(hc); } else { NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type"); } }
void BaseMediaResource::SetLoadInBackground(bool aLoadInBackground) { if (aLoadInBackground == mLoadInBackground) { return; } mLoadInBackground = aLoadInBackground; if (!mChannel) { // No channel, resource is probably already loaded. return; } MediaDecoderOwner* owner = mCallback->GetMediaOwner(); if (!owner) { NS_WARNING("Null owner in MediaResource::SetLoadInBackground()"); return; } dom::HTMLMediaElement* element = owner->GetMediaElement(); if (!element) { NS_WARNING("Null element in MediaResource::SetLoadInBackground()"); return; } bool isPending = false; if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) && isPending) { nsLoadFlags loadFlags; DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags); NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!"); if (aLoadInBackground) { loadFlags |= nsIRequest::LOAD_BACKGROUND; } else { loadFlags &= ~nsIRequest::LOAD_BACKGROUND; } ModifyLoadFlags(loadFlags); } }
nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener) { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER); NS_ASSERTION(!mListener, "Listener should have been removed by now"); if (aStreamListener) { *aStreamListener = nullptr; } if (mByteRange.IsNull()) { // We're not making a byte range request, so set the content length, // if it's available as an HTTP header. This ensures that MediaResource // wrapping objects for platform libraries that expect to know // the length of a resource can get it before OnStartRequest() fires. nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel); if (hc) { int64_t cl = -1; if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) { mCacheStream.NotifyDataLength(cl); } } } mListener = new Listener(this); NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY); if (aStreamListener) { *aStreamListener = mListener; NS_ADDREF(*aStreamListener); } else { mChannel->SetNotificationCallbacks(mListener.get()); nsCOMPtr<nsIStreamListener> listener = mListener.get(); // Ensure that if we're loading cross domain, that the server is sending // an authorizing Access-Control header. MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); if (element->ShouldCheckAllowOrigin()) { nsRefPtr<nsCORSListenerProxy> crossSiteListener = new nsCORSListenerProxy(mListener, element->NodePrincipal(), false); nsresult rv = crossSiteListener->Init(mChannel); listener = crossSiteListener; NS_ENSURE_TRUE(crossSiteListener, NS_ERROR_OUT_OF_MEMORY); NS_ENSURE_SUCCESS(rv, rv); } else { nsresult rv = nsContentUtils::GetSecurityManager()-> CheckLoadURIWithPrincipal(element->NodePrincipal(), mURI, nsIScriptSecurityManager::STANDARD); NS_ENSURE_SUCCESS(rv, rv); } SetupChannelHeaders(); nsresult rv = mChannel->AsyncOpen(listener, nullptr); NS_ENSURE_SUCCESS(rv, rv); // Tell the media element that we are fetching data from a channel. element->DownloadResumed(true); } return NS_OK; }
nsresult RtspMediaResource::OnConnected(uint8_t aTrackIdx, nsIStreamingProtocolMetaData *meta) { if (mIsConnected) { for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) { mTrackBuffer[i]->Start(); } return NS_OK; } uint8_t tracks; mMediaStreamController->GetTotalTracks(&tracks); // If the preference of RTSP video feature is not enabled and the streaming is // video, we give up moving forward. if (!IsVideoEnabled() && IsVideo(tracks, meta)) { // Give up, report error to media element. nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError); NS_DispatchToMainThread(event); return NS_ERROR_FAILURE; } uint64_t duration = 0; for (int i = 0; i < tracks; ++i) { nsCString rtspTrackId("RtspTrack"); rtspTrackId.AppendInt(i); nsCOMPtr<nsIStreamingProtocolMetaData> trackMeta; mMediaStreamController->GetTrackMetaData(i, getter_AddRefs(trackMeta)); MOZ_ASSERT(trackMeta); trackMeta->GetDuration(&duration); // Here is a heuristic to estimate the slot size. // For video track, calculate the width*height. // For audio track, use the BUFFER_SLOT_DEFAULT_SIZE because the w*h is 0. // Finally clamp them into (BUFFER_SLOT_DEFAULT_SIZE,BUFFER_SLOT_MAX_SIZE) uint32_t w, h; uint32_t slotSize; trackMeta->GetWidth(&w); trackMeta->GetHeight(&h); slotSize = clamped((int32_t)(w * h), BUFFER_SLOT_DEFAULT_SIZE, BUFFER_SLOT_MAX_SIZE); mTrackBuffer.AppendElement(new RtspTrackBuffer(rtspTrackId.get(), i, slotSize)); mTrackBuffer[i]->Start(); } if (!mDecoder) { return NS_ERROR_FAILURE; } // If the duration is 0, imply the stream is live stream. if (duration) { // Not live stream. mRealTime = false; mDecoder->SetInfinite(false); mDecoder->SetDuration(duration); } else { // Live stream. // Check the preference "media.realtime_decoder.enabled". if (!Preferences::GetBool("media.realtime_decoder.enabled", false)) { // Give up, report error to media element. nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError); NS_DispatchToMainThread(event); return NS_ERROR_FAILURE; } else { mRealTime = true; bool seekable = false; mDecoder->SetInfinite(true); mDecoder->SetMediaSeekable(seekable); } } // Fires an initial progress event and sets up the stall counter so stall events // fire if no download occurs within the required time frame. mDecoder->Progress(false); MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); element->FinishDecoderSetup(mDecoder, this); mIsConnected = true; return NS_OK; }
nsresult ChannelMediaResource::OnStartRequest(nsIRequest* aRequest) { NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!"); MediaDecoderOwner* owner = mDecoder->GetMediaOwner(); NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); nsresult status; nsresult rv = aRequest->GetStatus(&status); NS_ENSURE_SUCCESS(rv, rv); if (element->ShouldCheckAllowOrigin()) { // If the request was cancelled by nsCORSListenerProxy due to failing // the CORS security check, send an error through to the media element. if (status == NS_ERROR_DOM_BAD_URI) { mDecoder->NetworkError(); return NS_ERROR_DOM_BAD_URI; } } nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest); bool seekable = false; if (hc) { uint32_t responseStatus = 0; hc->GetResponseStatus(&responseStatus); bool succeeded = false; hc->GetRequestSucceeded(&succeeded); if (!succeeded && NS_SUCCEEDED(status)) { // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error. // We might get this on a seek. // (Note that lower-level errors indicated by NS_FAILED(status) are // handled in OnStopRequest.) // A 416 error should treated as EOF here... it's possible // that we don't get Content-Length, we read N bytes, then we // suspend and resume, the resume reopens the channel and we seek to // offset N, but there are no more bytes, so we get a 416 // "Requested Range Not Satisfiable". if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) { // OnStopRequest will not be fired, so we need to do some of its // work here. mCacheStream.NotifyDataEnded(status); } else { mDecoder->NetworkError(); } // This disconnects our listener so we don't get any more data. We // certainly don't want an error page to end up in our cache! CloseChannel(); return NS_OK; } nsAutoCString ranges; hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"), ranges); bool acceptsRanges = ranges.EqualsLiteral("bytes"); // True if this channel will not return an unbounded amount of data bool dataIsBounded = false; int64_t contentLength = -1; hc->GetContentLength(&contentLength); if (contentLength >= 0 && responseStatus == HTTP_OK_CODE) { // "OK" status means Content-Length is for the whole resource. // Since that's bounded, we know we have a finite-length resource. dataIsBounded = true; } if (mOffset == 0) { // Look for duration headers from known Ogg content systems. // In the case of multiple options for obtaining the duration // the order of precedence is: // 1) The Media resource metadata if possible (done by the decoder itself). // 2) Content-Duration message header. // 3) X-AMZ-Meta-Content-Duration. // 4) X-Content-Duration. // 5) Perform a seek in the decoder to find the value. nsAutoCString durationText; nsresult ec = NS_OK; rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText); if (NS_FAILED(rv)) { rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText); } if (NS_FAILED(rv)) { rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText); } // If there is a Content-Duration header with a valid value, record // the duration. if (NS_SUCCEEDED(rv)) { double duration = durationText.ToDouble(&ec); if (ec == NS_OK && duration >= 0) { mDecoder->SetDuration(duration); // We know the resource must be bounded. dataIsBounded = true; } } } // Assume Range requests have a bounded upper limit unless the // Content-Range header tells us otherwise. bool boundedSeekLimit = true; // Check response code for byte-range requests (seeking, chunk requests). if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) { // Parse Content-Range header. int64_t rangeStart = 0; int64_t rangeEnd = 0; int64_t rangeTotal = 0; rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal); if (NS_FAILED(rv)) { // Content-Range header text should be parse-able. CMLOG("Error processing \'Content-Range' for " "HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]", rv, hc.get(), mDecoder); mDecoder->NetworkError(); CloseChannel(); return NS_OK; } // Give some warnings if the ranges are unexpected. // XXX These could be error conditions. NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart, "response range start does not match request"); NS_WARN_IF_FALSE(mOffset == rangeStart, "response range start does not match current offset"); NS_WARN_IF_FALSE(mByteRange.mEnd == rangeEnd, "response range end does not match request"); // Notify media cache about the length and start offset of data received. // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage. // For now, tell the decoder that the stream is infinite. if (rangeTotal == -1) { boundedSeekLimit = false; } else { mCacheStream.NotifyDataLength(rangeTotal); } mCacheStream.NotifyDataStarted(rangeStart); mOffset = rangeStart; // We received 'Content-Range', so the server accepts range requests. acceptsRanges = true; } else if (((mOffset > 0) || !mByteRange.IsNull()) && (responseStatus == HTTP_OK_CODE)) { // If we get an OK response but we were seeking, or requesting a byte // range, then we have to assume that seeking doesn't work. We also need // to tell the cache that it's getting data for the start of the stream. mCacheStream.NotifyDataStarted(0); mOffset = 0; // The server claimed it supported range requests. It lied. acceptsRanges = false; } else if (mOffset == 0 && (responseStatus == HTTP_OK_CODE || responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) { if (contentLength >= 0) { mCacheStream.NotifyDataLength(contentLength); } } // XXX we probably should examine the Content-Range header in case // the server gave us a range which is not quite what we asked for // If we get an HTTP_OK_CODE response to our byte range request, // and the server isn't sending Accept-Ranges:bytes then we don't // support seeking. seekable = responseStatus == HTTP_PARTIAL_RESPONSE_CODE || acceptsRanges; if (seekable && boundedSeekLimit) { // If range requests are supported, and we did not see an unbounded // upper range limit, we assume the resource is bounded. dataIsBounded = true; } mDecoder->SetInfinite(!dataIsBounded); } mDecoder->SetTransportSeekable(seekable); mCacheStream.SetTransportSeekable(seekable); { MutexAutoLock lock(mLock); mIsTransportSeekable = seekable; mChannelStatistics->Start(); } mReopenOnError = false; // If we are seeking to get metadata, because we are playing an OGG file, // ignore if the channel gets closed without us suspending it explicitly. We // don't want to tell the element that the download has finished whereas we // just happended to have reached the end of the media while seeking. mIgnoreClose = mSeekingForMetadata; if (mSuspendCount > 0) { // Re-suspend the channel if it needs to be suspended // No need to call PossiblySuspend here since the channel is // definitely in the right state for us in OnStartRequest. mChannel->Suspend(); mIgnoreResume = false; } // Fires an initial progress event and sets up the stall counter so stall events // fire if no download occurs within the required time frame. mDecoder->Progress(false); return NS_OK; }
nsresult ChannelMediaResource::OnStartRequest(nsIRequest* aRequest) { NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!"); MediaDecoderOwner* owner = mCallback->GetMediaOwner(); NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); dom::HTMLMediaElement* element = owner->GetMediaElement(); NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); nsresult status; nsresult rv = aRequest->GetStatus(&status); NS_ENSURE_SUCCESS(rv, rv); if (status == NS_BINDING_ABORTED) { // Request was aborted before we had a chance to receive any data, or // even an OnStartRequest(). Close the channel. This is important, as // we don't want to mess up our state, as if we're cloned that would // cause the clone to copy incorrect metadata (like whether we're // infinite for example). CloseChannel(); return status; } if (element->ShouldCheckAllowOrigin()) { // If the request was cancelled by nsCORSListenerProxy due to failing // the CORS security check, send an error through to the media element. if (status == NS_ERROR_DOM_BAD_URI) { mCallback->NotifyNetworkError(); return NS_ERROR_DOM_BAD_URI; } } nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest); bool seekable = false; if (hc) { uint32_t responseStatus = 0; hc->GetResponseStatus(&responseStatus); bool succeeded = false; hc->GetRequestSucceeded(&succeeded); if (!succeeded && NS_SUCCEEDED(status)) { // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error. // We might get this on a seek. // (Note that lower-level errors indicated by NS_FAILED(status) are // handled in OnStopRequest.) // A 416 error should treated as EOF here... it's possible // that we don't get Content-Length, we read N bytes, then we // suspend and resume, the resume reopens the channel and we seek to // offset N, but there are no more bytes, so we get a 416 // "Requested Range Not Satisfiable". if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) { // OnStopRequest will not be fired, so we need to do some of its // work here. mCacheStream.NotifyDataEnded(status); } else { mCallback->NotifyNetworkError(); } // This disconnects our listener so we don't get any more data. We // certainly don't want an error page to end up in our cache! CloseChannel(); return NS_OK; } nsAutoCString ranges; hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"), ranges); bool acceptsRanges = ranges.EqualsLiteral("bytes"); // True if this channel will not return an unbounded amount of data bool dataIsBounded = false; int64_t contentLength = -1; hc->GetContentLength(&contentLength); if (contentLength >= 0 && responseStatus == HTTP_OK_CODE) { // "OK" status means Content-Length is for the whole resource. // Since that's bounded, we know we have a finite-length resource. dataIsBounded = true; } // Assume Range requests have a bounded upper limit unless the // Content-Range header tells us otherwise. bool boundedSeekLimit = true; // Check response code for byte-range requests (seeking, chunk requests). if (responseStatus == HTTP_PARTIAL_RESPONSE_CODE) { // Parse Content-Range header. int64_t rangeStart = 0; int64_t rangeEnd = 0; int64_t rangeTotal = 0; rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal); // We received 'Content-Range', so the server accepts range requests. bool gotRangeHeader = NS_SUCCEEDED(rv); if (gotRangeHeader) { // We received 'Content-Range', so the server accepts range requests. // Notify media cache about the length and start offset of data received. // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage. // For now, tell the decoder that the stream is infinite. if (rangeTotal == -1) { boundedSeekLimit = false; } else { contentLength = std::max(contentLength, rangeTotal); } // Give some warnings if the ranges are unexpected. // XXX These could be error conditions. NS_WARN_IF_FALSE(mOffset == rangeStart, "response range start does not match current offset"); mOffset = rangeStart; mCacheStream.NotifyDataStarted(rangeStart); } acceptsRanges = gotRangeHeader; } else if (mOffset > 0 && responseStatus == HTTP_OK_CODE) { // If we get an OK response but we were seeking, or requesting a byte // range, then we have to assume that seeking doesn't work. We also need // to tell the cache that it's getting data for the start of the stream. mCacheStream.NotifyDataStarted(0); mOffset = 0; // The server claimed it supported range requests. It lied. acceptsRanges = false; } if (mOffset == 0 && contentLength >= 0 && (responseStatus == HTTP_OK_CODE || responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) { mCacheStream.NotifyDataLength(contentLength); } // XXX we probably should examine the Content-Range header in case // the server gave us a range which is not quite what we asked for // If we get an HTTP_OK_CODE response to our byte range request, // and the server isn't sending Accept-Ranges:bytes then we don't // support seeking. seekable = acceptsRanges; if (seekable && boundedSeekLimit) { // If range requests are supported, and we did not see an unbounded // upper range limit, we assume the resource is bounded. dataIsBounded = true; } mCallback->SetInfinite(!dataIsBounded); } mCacheStream.SetTransportSeekable(seekable); { MutexAutoLock lock(mLock); mIsTransportSeekable = seekable; mChannelStatistics->Start(); } mReopenOnError = false; mIgnoreClose = false; mSuspendAgent.UpdateSuspendedStatusIfNeeded(); // Fires an initial progress event. owner->DownloadProgressed(); return NS_OK; }