MediaElementEventRunners.cpp (8875B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "MediaElementEventRunners.h" 6 7 #include <stdint.h> 8 9 #include "MediaProfilerMarkers.h" 10 #include "mozilla/Casting.h" 11 #include "mozilla/FlowMarkers.h" 12 #include "mozilla/ProfilerState.h" 13 #include "mozilla/dom/HTMLMediaElement.h" 14 #include "mozilla/dom/HTMLVideoElement.h" 15 #include "mozilla/dom/MediaError.h" 16 #include "mozilla/dom/TimeRanges.h" 17 18 extern mozilla::LazyLogModule gMediaElementEventsLog; 19 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg) 20 21 namespace mozilla::dom { 22 23 nsMediaEventRunner::nsMediaEventRunner(const char* aName, 24 HTMLMediaElement* aElement, 25 const nsAString& aEventName) 26 : mElement(aElement), 27 mName(aName), 28 mEventName(aEventName), 29 mLoadID(mElement->GetCurrentLoadID()) {} 30 31 bool nsMediaEventRunner::IsCancelled() const { 32 return !mElement || mElement->GetCurrentLoadID() != mLoadID; 33 } 34 35 nsresult nsMediaEventRunner::FireEvent(const nsAString& aName) { 36 nsresult rv = NS_OK; 37 if (mElement) { 38 ReportProfilerMarker(); 39 rv = RefPtr { mElement } -> FireEvent(aName); 40 } 41 return rv; 42 } 43 44 void nsMediaEventRunner::ReportProfilerMarker() { 45 if (!profiler_is_collecting_markers()) { 46 return; 47 } 48 // Report the buffered range. 49 if (mEventName.EqualsLiteral("progress")) { 50 RefPtr<TimeRanges> buffered = mElement->Buffered(); 51 if (buffered && buffered->Length() > 0) { 52 for (size_t i = 0; i < buffered->Length(); ++i) { 53 profiler_add_marker("progress", geckoprofiler::category::MEDIA_PLAYBACK, 54 {}, BufferedUpdateMarker{}, 55 AssertedCast<uint64_t>(buffered->Start(i) * 1000), 56 AssertedCast<uint64_t>(buffered->End(i) * 1000), 57 GetElementDurationMs(), 58 Flow::FromPointer(mElement.get())); 59 } 60 } 61 } else if (mEventName.EqualsLiteral("resize")) { 62 MOZ_ASSERT(mElement->HasVideo()); 63 auto mediaInfo = mElement->GetMediaInfo(); 64 profiler_add_marker("resize", geckoprofiler::category::MEDIA_PLAYBACK, {}, 65 VideoResizeMarker{}, mediaInfo.mVideo.mDisplay.width, 66 mediaInfo.mVideo.mDisplay.height, 67 Flow::FromPointer(mElement.get())); 68 } else if (mEventName.EqualsLiteral("loadedmetadata")) { 69 nsString src; 70 mElement->GetCurrentSrc(src); 71 auto mediaInfo = mElement->GetMediaInfo(); 72 profiler_add_marker( 73 "loadedmetadata", geckoprofiler::category::MEDIA_PLAYBACK, {}, 74 MetadataMarker{}, src, 75 mediaInfo.HasAudio() ? mediaInfo.mAudio.mMimeType : "none"_ns, 76 mediaInfo.HasVideo() ? mediaInfo.mVideo.mMimeType : "none"_ns, 77 Flow::FromPointer(mElement.get())); 78 } else if (mEventName.EqualsLiteral("error")) { 79 auto* error = mElement->GetError(); 80 nsString message; 81 error->GetMessage(message); 82 profiler_add_marker("error", geckoprofiler::category::MEDIA_PLAYBACK, {}, 83 ErrorMarker{}, message, 84 Flow::FromPointer(mElement.get())); 85 } else { 86 auto eventName = NS_ConvertUTF16toUTF8(mEventName); 87 PROFILER_MARKER(eventName, MEDIA_PLAYBACK, {}, FlowMarker, 88 Flow::FromPointer(mElement.get())); 89 } 90 } 91 92 uint64_t nsMediaEventRunner::GetElementDurationMs() const { 93 MOZ_ASSERT(!IsCancelled()); 94 double duration = mElement->Duration(); 95 96 if (duration == std::numeric_limits<double>::infinity()) { 97 return std::numeric_limits<uint64_t>::max(); 98 } 99 100 if (std::isnan(duration) || duration <= 0) { 101 // Duration is unknown or invalid 102 return 0; 103 } 104 return AssertedCast<uint64_t>(duration * 1000); 105 } 106 107 NS_IMPL_CYCLE_COLLECTION(nsMediaEventRunner, mElement) 108 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMediaEventRunner) 109 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMediaEventRunner) 110 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMediaEventRunner) 111 NS_INTERFACE_MAP_ENTRY(nsINamed) 112 NS_INTERFACE_MAP_ENTRY(nsIRunnable) 113 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable) 114 NS_INTERFACE_MAP_END 115 116 NS_IMETHODIMP nsAsyncEventRunner::Run() { 117 // Silently cancel if our load has been cancelled or element has been CCed. 118 return IsCancelled() ? NS_OK : FireEvent(mEventName); 119 } 120 121 nsResolveOrRejectPendingPlayPromisesRunner:: 122 nsResolveOrRejectPendingPlayPromisesRunner( 123 HTMLMediaElement* aElement, nsTArray<RefPtr<PlayPromise>>&& aPromises, 124 nsresult aError) 125 : nsMediaEventRunner("nsResolveOrRejectPendingPlayPromisesRunner", 126 aElement), 127 mPromises(std::move(aPromises)), 128 mError(aError) { 129 mElement->mPendingPlayPromisesRunners.AppendElement(this); 130 } 131 132 void nsResolveOrRejectPendingPlayPromisesRunner::ResolveOrReject() { 133 if (NS_SUCCEEDED(mError)) { 134 PlayPromise::ResolvePromisesWithUndefined(mPromises); 135 } else { 136 PlayPromise::RejectPromises(mPromises, mError); 137 } 138 } 139 140 NS_IMETHODIMP nsResolveOrRejectPendingPlayPromisesRunner::Run() { 141 if (!IsCancelled()) { 142 ResolveOrReject(); 143 } 144 145 mElement->mPendingPlayPromisesRunners.RemoveElement(this); 146 return NS_OK; 147 } 148 149 NS_IMETHODIMP nsNotifyAboutPlayingRunner::Run() { 150 if (!IsCancelled()) { 151 FireEvent(u"playing"_ns); 152 } 153 return nsResolveOrRejectPendingPlayPromisesRunner::Run(); 154 } 155 156 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsResolveOrRejectPendingPlayPromisesRunner, 157 nsMediaEventRunner, mPromises) 158 NS_IMPL_ADDREF_INHERITED(nsResolveOrRejectPendingPlayPromisesRunner, 159 nsMediaEventRunner) 160 NS_IMPL_RELEASE_INHERITED(nsResolveOrRejectPendingPlayPromisesRunner, 161 nsMediaEventRunner) 162 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 163 nsResolveOrRejectPendingPlayPromisesRunner) 164 NS_INTERFACE_MAP_END_INHERITING(nsMediaEventRunner) 165 166 NS_IMETHODIMP nsSourceErrorEventRunner::Run() { 167 // Silently cancel if our load has been cancelled. 168 if (IsCancelled()) { 169 return NS_OK; 170 } 171 LOG_EVENT(LogLevel::Debug, 172 ("%p Dispatching simple event source error", mElement.get())); 173 if (profiler_is_collecting_markers()) { 174 profiler_add_marker("sourceerror", geckoprofiler::category::MEDIA_PLAYBACK, 175 {}, ErrorMarker{}, mErrorDetails, 176 Flow::FromPointer(mElement.get())); 177 } 178 return nsContentUtils::DispatchTrustedEvent(mElement->OwnerDoc(), mSource, 179 u"error"_ns, CanBubble::eNo, 180 Cancelable::eNo); 181 } 182 183 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSourceErrorEventRunner, nsMediaEventRunner, 184 mSource) 185 NS_IMPL_ADDREF_INHERITED(nsSourceErrorEventRunner, nsMediaEventRunner) 186 NS_IMPL_RELEASE_INHERITED(nsSourceErrorEventRunner, nsMediaEventRunner) 187 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSourceErrorEventRunner) 188 NS_INTERFACE_MAP_END_INHERITING(nsMediaEventRunner) 189 190 NS_IMETHODIMP nsTimeupdateRunner::Run() { 191 if (IsCancelled() || !ShouldDispatchTimeupdate()) { 192 return NS_OK; 193 } 194 // After dispatching `timeupdate`, if the timeupdate event listener takes lots 195 // of time then we end up spending all time handling just timeupdate events. 196 // The spec is vague in this situation, so we choose to update time after we 197 // dispatch the event in order to solve that issue. 198 nsresult rv = FireEvent(mEventName); 199 if (NS_WARN_IF(NS_FAILED(rv))) { 200 LOG_EVENT(LogLevel::Debug, 201 ("%p Failed to dispatch 'timeupdate'", mElement.get())); 202 } else { 203 mElement->UpdateLastTimeupdateDispatchTime(); 204 } 205 return rv; 206 } 207 208 bool nsTimeupdateRunner::ShouldDispatchTimeupdate() const { 209 if (mIsMandatory) { 210 return true; 211 } 212 213 // If the main thread is busy, tasks may be delayed and dispatched at 214 // unexpected times. Ensure we don't dispatch `timeupdate` more often 215 // than once per `TIMEUPDATE_MS`. 216 const TimeStamp& lastTime = mElement->LastTimeupdateDispatchTime(); 217 return lastTime.IsNull() || TimeStamp::Now() - lastTime > 218 TimeDuration::FromMilliseconds(TIMEUPDATE_MS); 219 } 220 221 void nsTimeupdateRunner::ReportProfilerMarker() { 222 if (!profiler_is_collecting_markers()) { 223 return; 224 } 225 auto* videoElement = mElement->AsHTMLVideoElement(); 226 profiler_add_marker("timeupdate", geckoprofiler::category::MEDIA_PLAYBACK, {}, 227 TimeUpdateMarker{}, 228 AssertedCast<uint64_t>(mElement->CurrentTime() * 1000), 229 GetElementDurationMs(), 230 videoElement ? videoElement->MozPaintedFrames() : 0, 231 Flow::FromPointer(mElement.get())); 232 } 233 234 #undef LOG_EVENT 235 } // namespace mozilla::dom