CaptureTask.cpp (5730B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "CaptureTask.h" 8 9 #include "VideoSegment.h" 10 #include "gfxUtils.h" 11 #include "mozilla/SchedulerGroup.h" 12 #include "mozilla/dom/BlobImpl.h" 13 #include "mozilla/dom/ImageCapture.h" 14 #include "mozilla/dom/ImageCaptureError.h" 15 #include "mozilla/dom/ImageEncoder.h" 16 #include "mozilla/dom/MediaStreamTrack.h" 17 #include "mozilla/dom/VideoStreamTrack.h" 18 #include "nsThreadUtils.h" 19 20 namespace mozilla { 21 22 class CaptureTask::MediaTrackEventListener : public MediaTrackListener { 23 public: 24 explicit MediaTrackEventListener(CaptureTask* aCaptureTask) 25 : mCaptureTask(aCaptureTask) {}; 26 27 // MediaTrackListener methods. 28 void NotifyEnded(MediaTrackGraph* aGraph) override { 29 mCaptureTask->PostTrackEndEvent(); 30 } 31 32 private: 33 CaptureTask* mCaptureTask; 34 }; 35 36 CaptureTask::CaptureTask(dom::ImageCapture* aImageCapture) 37 : mImageCapture(aImageCapture), 38 mEventListener(new MediaTrackEventListener(this)), 39 mImageGrabbedOrTrackEnd(false), 40 mPrincipalChanged(false) {} 41 42 nsresult CaptureTask::TaskComplete(already_AddRefed<dom::BlobImpl> aBlobImpl, 43 nsresult aRv) { 44 MOZ_ASSERT(NS_IsMainThread()); 45 46 DetachTrack(); 47 48 nsresult rv; 49 RefPtr<dom::BlobImpl> blobImpl(aBlobImpl); 50 51 // We have to set the parent because the blob has been generated with a valid 52 // one. 53 RefPtr<dom::Blob> blob; 54 if (blobImpl) { 55 blob = dom::Blob::Create(mImageCapture->GetOwnerGlobal(), blobImpl); 56 if (NS_WARN_IF(!blob)) { 57 return NS_ERROR_FAILURE; 58 } 59 } 60 61 if (mPrincipalChanged) { 62 aRv = NS_ERROR_DOM_SECURITY_ERR; 63 IC_LOG("MediaStream principal should not change during TakePhoto()."); 64 } 65 66 if (NS_SUCCEEDED(aRv)) { 67 rv = mImageCapture->PostBlobEvent(blob); 68 } else { 69 rv = 70 mImageCapture->PostErrorEvent(dom::ImageCaptureError::PHOTO_ERROR, aRv); 71 } 72 73 // Ensure ImageCapture dereference on main thread here because the TakePhoto() 74 // sequences stopped here. 75 mImageCapture = nullptr; 76 77 return rv; 78 } 79 80 void CaptureTask::AttachTrack() { 81 MOZ_ASSERT(NS_IsMainThread()); 82 83 dom::MediaStreamTrack* track = mImageCapture->GetVideoStreamTrack(); 84 track->AddPrincipalChangeObserver(this); 85 track->AddListener(mEventListener.get()); 86 track->AddDirectListener(this); 87 } 88 89 void CaptureTask::DetachTrack() { 90 MOZ_ASSERT(NS_IsMainThread()); 91 92 dom::MediaStreamTrack* track = mImageCapture->GetVideoStreamTrack(); 93 track->RemovePrincipalChangeObserver(this); 94 track->RemoveListener(mEventListener.get()); 95 track->RemoveDirectListener(this); 96 } 97 98 void CaptureTask::PrincipalChanged(dom::MediaStreamTrack* aMediaStreamTrack) { 99 MOZ_ASSERT(NS_IsMainThread()); 100 mPrincipalChanged = true; 101 } 102 103 void CaptureTask::NotifyRealtimeTrackData(MediaTrackGraph* aGraph, 104 TrackTime aTrackOffset, 105 const MediaSegment& aMedia) { 106 MOZ_ASSERT(aMedia.GetType() == MediaSegment::VIDEO); 107 const VideoSegment& video = static_cast<const VideoSegment&>(aMedia); 108 109 // Callback for encoding complete, it calls on main thread. 110 class EncodeComplete : public dom::EncodeCompleteCallback { 111 public: 112 explicit EncodeComplete(CaptureTask* aTask) : mTask(aTask) {} 113 114 bool CanBeDeletedOnAnyThread() override { 115 // EncodeComplete is used from the main thread only. 116 return false; 117 } 118 119 nsresult ReceiveBlobImpl( 120 already_AddRefed<dom::BlobImpl> aBlobImpl) override { 121 MOZ_ASSERT(NS_IsMainThread()); 122 RefPtr<dom::BlobImpl> blobImpl(aBlobImpl); 123 mTask->TaskComplete(blobImpl.forget(), NS_OK); 124 mTask = nullptr; 125 return NS_OK; 126 } 127 128 protected: 129 RefPtr<CaptureTask> mTask; 130 }; 131 132 for (VideoSegment::ConstChunkIterator iter(video); !iter.IsEnded(); 133 iter.Next()) { 134 VideoChunk chunk = *iter; 135 136 // Extract the first valid video frame. 137 VideoFrame frame; 138 if (chunk.IsNull()) { 139 continue; 140 } 141 142 RefPtr<layers::Image> image; 143 if (chunk.mFrame.GetForceBlack()) { 144 // Create a black image. 145 image = VideoFrame::CreateBlackImage(chunk.mFrame.GetIntrinsicSize()); 146 } else { 147 image = chunk.mFrame.GetImage(); 148 } 149 if (!image) { 150 MOZ_ASSERT(image); 151 continue; 152 } 153 154 bool wasGrabbed = mImageGrabbedOrTrackEnd.exchange(true); 155 if (wasGrabbed) { 156 return; 157 } 158 159 // Encode image. 160 nsresult rv; 161 nsAutoString type(u"image/jpeg"_ns); 162 nsAutoString options; 163 rv = dom::ImageEncoder::ExtractDataFromLayersImageAsync( 164 type, options, false, image, CanvasUtils::ImageExtraction::Unrestricted, 165 VoidCString(), new EncodeComplete(this)); 166 if (NS_FAILED(rv)) { 167 PostTrackEndEvent(); 168 } 169 } 170 } 171 172 void CaptureTask::PostTrackEndEvent() { 173 bool wasGrabbed = mImageGrabbedOrTrackEnd.exchange(true); 174 if (wasGrabbed) { 175 return; 176 } 177 178 // Got track end or finish event, stop the task. 179 class TrackEndRunnable : public Runnable { 180 public: 181 explicit TrackEndRunnable(CaptureTask* aTask) 182 : mozilla::Runnable("TrackEndRunnable"), mTask(aTask) {} 183 184 NS_IMETHOD Run() override { 185 mTask->TaskComplete(nullptr, NS_ERROR_FAILURE); 186 mTask = nullptr; 187 return NS_OK; 188 } 189 190 protected: 191 RefPtr<CaptureTask> mTask; 192 }; 193 194 IC_LOG("Got MediaTrack track removed or finished event."); 195 nsCOMPtr<nsIRunnable> event = new TrackEndRunnable(this); 196 SchedulerGroup::Dispatch(event.forget()); 197 } 198 199 } // namespace mozilla