Files
palemoon27/dom/media/MediaDecoderStateMachine.cpp
T
roytam1 c6e1bfdcf2 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1261578 - Part 1. Correct text color in selection range; r=jfkthame (6895ebe313)
- Bug 1261578 - Part 2. Correct text decoration color; r=jfkthame (348ba5a946)
- Bug 1261578 - Part 3. Correct MathML text color; r=jfkthame (99dfb99af4)
- Bug 1261578 - Part 4. Correct text overflow color; r=jfkthame (96318becf6)
- Bug 1261578 - Part 5. web-platform-test reftest; r=jfkthame (781bba0317)
- Bug 1110460, part 1 - Add operator= to nsCSSValuePair. r=dholbert (7d836b786b)
- Bug 1110460, part 2 - Move AddPositions() further up StyleAnimationValue.cpp and add asserts. r=dholbert (5f00295917)
- Bug 1110460, part 3 - Factor out a AddCSSValuePair helper in StyleAnimationValue.cpp. r=dholbert (9947835334)
- Bug 1110460, part 4 - Fix MOZ_ASSERT bug in nsComputedDOMStyle.cpp. r=dholbert (5403c0c377)
- Bug 1110460, part 5 - Factor out a nsStyleBasicShape::GetShapeTypeName method. r=dholbert (d27623d42f)
- Bug 1110460, part 6 - Make StyleAnimationValue support css::URLValue backed nsCSSValue objects. r=dholbert (dc006d1cae)
- Bug 1110460, part 7 - Factor out code for adding two nsCSSValuePairList lists. r=dholbert (f27f780005)
- Bug 1260655 - Allow StyleAnimationValue::UncomputeValue to produce values whose storage is independent of the passed-in computed value; r=heycam (abcc77ede5)
- Bug 1110460, part 8 - Support CSS animation of clip-path basic shapes. r=dholbert (cfe5bf883b)
- Bug 1238243 - Reftests involving possibly-ignorable hangul choseong filler. r=xidorn Bug 1238243 followup - Mark test 1238243-2.html as random on OS X 10.6, due to dependency on available fonts. Bug 1239564 - Add reftest for mask layer composition. r=roc (bb3bf56d52)
- Bug 1260543 - Treat currentcolor as computed value which is not interpolatable with actual color for text-emphasis-color and -webkit-text-fill-color. r=birtles (7b90f37e2f)
- Bug 1260543 followup - Fix the function name in test_transitions_per_property.html. DONTBUILD (1362d30c6a)
- Bug 1248708 - Part1: parse and compute -webkit-text-stroke property. r=heycam (c77332031d)
- Bug 1248708 - Part2.1: use mfbt/TypedEnumBits.h for DrawMode. r=jwatt (55bfbdf5dd)
- Bug 1266101, part 1 - Remove various bits of dead code related to painting SVG text. r=heycam (c5140a911c)
- Bug 1266101, part 2 - Remove most of the GLYPH_STROKE/GLYPH_STROKE_UNDERNEATH checking code. r=heycam (6d8f17bc19)
- Bug 1248708 - Part2.2: render -webkit-text-stroke property. r=jfkthame (bccc308ddc)
- Bug 1248708 - Part3: add reftests. r=jfkthame (0fc0864749)
- Bug 1248708 followup: Use MOZ_ARRAY_LENGTH instead of ArrayLength for expression used at compile time, to fix MSVC 2013 bustage. (trivial/bustage-fix, no review) (25cecd7585)
- Bug 1266341 - Pass the right kind of enum constants for script codes. r=masayuki (723e784a37)
- Bug 1245866 - A better buffer size check in nsIDNService::IDNA2008ToUnicode, r=jfkthame (683db7d93c)
- Bug 1233610 - IDN service should return NS_ERROR_MALFORMED_URI instead of NS_ERROR_FAILURE r=mcmanus (d70d4425ca)
- Bug 1266391 - Introduce an enum class mozilla::unicode::Script, and use this instead of bare integers to specify script codes for better type checking. r=masayuki (363a23049f)
- fix 1389436 backport (a8f96f6b33)
- Bug 1258228 - create special child accessibles in owner's constructor, part1, r=marcoz (2bbb819f76)
- Bug 1258228 - create special child accessibles in owner's constructor, part2, r=marcoz (5e20a7761a)
- Bug 1258228 - create special child accessibles in owner's constructor, part3, r=marcoz (6574efb1e7)
- Bug 1258228 - create special child accessibles in owner's constructor, part4, r=marcoz (15420c5a12)
- Bug 1188818 - DataTransfer must deal with nsIFile of directories, r=jwatt (715a857dd1)
- Bug 1126295 - Move TestAtoms.cpp to gtest and enable it; r=froydnj (9275ebeb3c)
- Bug 1257128 (part 1) - Remove nsIAtom.equalsUTF8. r=froydnj. (17238a04e6)
- Bug 1257128 (part 2) - Remove nsIAtomService.get{,Permanent}AtomUTF8. r=froydnj. (f4131f1024)
- Bug 1257128 (part 3) - Remove unused createAtom() function in a test. r=tbsaunde. (e082c1bc17)
- Bug 1257128 (part 4) - Remove unused nsIAtomService references in toolkit/. r=froydnj. (5ddae71ae8)
- Bug 1257402 - Remove NS_NewPermanentAtom() and nsIAtomService.getPermanentAtom(). r=froydnj,kmag. (4b9ad7894e)
- Bug 1145395 - Pass Element& to nsHTMLEditor::RemoveElementIfNoStyleOrIdOrClass; r=ehsan (83080cb024)
- Bug 1140105 - Can't query for a specific font face when the selection is collapsed. r=ehsan (b534c04e7f)
- Bug 1145395 - Introduce an overload of nsHTMLEditor::RemoveStyleInside that takes nsIContent&; r=ehsan (c5cb3bfb1d)
- Bug 1170326 - nsHTMLCSSUtils::ParseLength should check if the input is an empty string, r=ehsan (1950f67c9f)
- Bug 1260871 - Remove do_GetAtom() and rename NS_NewAtom() as NS_Atomize(). r=erahm. (984bc596bf)
- Bug 1254128 - Make the Mozilla .lldbinit use the full path for topsrcdir. r=sparky (20856a94b4)
- Bug 1259659 - rename from InternalUIEvent.detail to InternalUIEvent.mDetail. r=masayuki (37165bd487)
- Bug 1249664 - Make stateful frames responsible for their own keys r=dbaron (03a4365aa1)
- Bug 1232338 - part 1, Add better mouse support to InputData MouseInput r=kats (8be234631e)
- Bug 1259655 - part 1 Rename WidgetEventTime::time to WidgetEventTime::mTime r=masayuki (cd679b408e)
- Bug 1235908 - measure total event wait and process time. r=smaug (4364242809)
- Bug 1259655 - part 2 Rename WidgetEventTime::timeStamp to WidgetEventTime::mTimeStamp r=masayuki (94a507904d)
- Bug 1263785 - Kill off the deprecated nsINode::GetCurrentDoc. r=baku (670e175017)
- Bug 1245748 - Remove no-longer-needed code for directly setting up properties in KeyframeEffect(ReadOnly) constructor; r=heycam (166ed7697c)
- Bug 1259285 - Part1 - Move CSS/Web Animations-specific visibility handling. r=birtles (13a4ac2b94)
- Bug 1259285 - Part2 - Add web-platform test for visibility handling. r=birtles (b7cee58cf7)
- Bug 1260655 - Add KeyframeEffectReadOnly::SetFrames; r=heycam (7da27828da)
- Bug 1260655 - Wrap lines in keyframe-effect/constructor.html to 80 chars; r=whitespace-only (32b6412db2)
- Bug 1260655 - Update keyframe-effect/constructor.html to no longer refer to PropertyIndexedKeyframes or Keyframe; r=heycam (dbe0acf873)
- Bug 1260655 - Return the stored Keyframe objects from GetFrames, when available ; r=heycam (5b8cbc1773)
- Bug 1245748 - Add a Move constructor to Keyframe; r=heycam (eeabc9396a)
- Bug 1260655 - Add an assignment operator to Keyframe that takes an rvalue reference; r=heycam (e81f1f6d7c)
- Bug 1260655 - Add methods to CSSAnimationBuilder to build a set of Keyframe objects; r=heycam (93e2a7b3aa)
- Bug 1260655 - Use CSSAnimationBuilder::BuildAnimationFrames to set up CSS animations using Keyframe objects; r=heycam (4df3ad20cd)
- Bug 1260655 - Drop some no-longer-needed code for setting up CSS animations using AnimationProperty objects; r=heycam (e1d8e020aa)
- Bug 1260655 - Add a copy constructor and copy assignment operator to Keyframe; r=heycam (d9a1ca5d24)
- Bug 1249564 - Part 1: Assign the parent object of AnimationEffectTiming(ReadOnly). r=birtles,bz (298bdcf084)
- Bug 1249564 - Part 2: Cycle collect AnimationEffectTimingReadOnly. r=birtles (25d959d057)
- Bug 1263063 - Part 1: Remove unnecessary clamping of TimingParams::mIterationStart, since it's guaranteed to be nonnegative. r=dholbert (35362749d7)
- Bug 1263063 - Part 2: Add an assertion to ensure that iteration count is nonnegative and finite. r=dholbert (7df13df139)
- Bug 1263063 - Part 3: Change logic in KeyFrameEffect to assume that TimingParams' iteration-count has already been validated as nonnegative & finite. r=dholbert (6dd3f0d984)
- Bug 1263063 - Part 4: Move ActiveDuration() into TimingParams. r=dholbert (6760bdeaa7)
- Bug 1263063 - Part 5: Introduce TimingParams::EndTime(). r=dholbert (af9a4ddd95)
- Bug 1260983 - Allow creating animations with a target element not bound to a document; r=heycam (7d71619cfb)
- Bug 1244591 - Part 1: Implement KeyframeEffect.setFrames r=birtles,smaug (e090fa68b6)
- Bug 1244591 - Part 2: Extract useful keyframes tests to a new file r=birtles (780797fe53)
- Bug 1244591 - Part 3: Add web-platform tests for KeyframeEffect.setFrames r=birtles (997d26427b)
- Bug 1259878 - remove unnecessary isSome(). r=birtles (cb21268a0a)
- Bug 1248532 - Part 1: steps-start does not produce correct value at the beginning of the interval. r=birtles (9131923815)
- Bug 1248532 - Part 2: Add tests for step-start. r=birtles (04ae44375d)
- Bug 1248532 - Part 3: add a test for step-end with iterationStart. r=birtles (769827746b)
- Bug 1266257 - Revise timing model calculations to use fraction-based approach; r=hiro (c1de969ef0)
- Bug 1260572 - Replace AnimValuesStyleRule::AddEmptyValue with an overload of AddValue that takes an rvalue reference; r=heycam (08e01a4cdc)
- Bug 1260572 - Use 50% switch behavior if StyleAnimationValue::Interpolate fails; r=heycam (e67754e770)
- Bug 1260976 - Remove the old AnimationProperty-based GetFrames; r=heycam (efc88117e6)
- Bug 1259248. Add an ArrayView class. r=botond (5bb9e22500)
- Bug 1265648 - Remove the global nsTextFrameTextRunCache, as it no longer serves any useful purpose. r=mats (3ffbf42634)
- Bug 1238243 - Don't filter out Hangul jamo fillers as 'ignorable', because the font may require them to provide advance width. r=xidorn (9fc2f82853)
- Bug 1265648 followup - Use an array of UniquePtr<> for mTextRunsToDelete. r=mats (8745a7566e)
- Bug 1263956 - Adopt the new harfbuzz API for char-to-glyph mapping functions. r=jrmuizel (1f061d7b0f)
- Bug 1265568 - Don't require component alpha layers for text with -moz-osx-font-smoothing:grayscale. r=jfkthame,mattwoodrow (f819591273)
- Bug 1199547 - Backout workaround from bug 722676 as it causes issues on at least OSX 10.10 and 10.11. r=mstange (c96fb3a69d)
- Bug 1265953, part 1 - Convert most of the cocoa widget code from nsAutoPtr to UniquePtr. r=mstange (74aa93eaf9)
- Bug 1249915 - Add ability to synthesize native touch events on GTK for mochitests. r=karlt (b734a366fb)
- Bug 1259670 Rename WidgetPluginEvent::retargetToFocusedDocument to WidgetPluginEvent::mRetargetToFocusedDocument r=smaug (24c12b3bb4)
- Bug 1249915 - Fix synthesized touch injection code on Windows to not apply the scale factor twice. r=jimm (b590300aa3)
- Bug 1250505 - Convert SynthesizeNativeTouchPoint and SynthesizeNativeTouchTap to take a LayoutDeviceIntPoint instead of a ScreenPoint. r=njn (8ddd066519)
- Bug 1256731 - patch 1 - Don't apply theme-dpi scaling to metrics of window border elements, because Windows doesn't respect per-monitor dpi scaling when it draws them. r=emk (582676e33e)
- Bug 1247935 - Part 1: double-buffer nsShmImage. r=jrmuizel (2392a1e916)
- Bug 1247935 - Part 2: use shared pixmaps with XShm for nsShmImage. r=jrmuizel (b3dd105fcc)
- Bug 1265953, part 2 - Convert much of the rest of the widget code from nsAutoPtr to UniquePtr. r=mstange (e0c1c49ef8)
- Bug 1265953, part 3 - Convert more widget code from nsAutoPtr to UniquePtr. r=mstange (17e1be57d2)
- Bug 1265953, part 4 - Convert nsMenuX::LoadSubMenu and nsMenuX::AddMenu to UniquePtr. r=mstange (f4ea50b802)
- Bug 1259664 part.1 Rename WidgetWheelEvent::deltaX to WidgetWheelEvent::mDeltaX r=smaug (8c7ef861eb)
- Bug 1259664 part.2 Rename WidgetWheelEvent::deltaY to WidgetWheelEvent::mDeltaY r=smaug (03529452fb)
- Bug 1259664 part.3 Rename WidgetWheelEvent::deltaZ to WidgetWheelEvent::mDeltaZ r=smaug (0d664b2900)
- Bug 1259664 part.4 Rename WidgetWheelEvent::deltaMode to WidgetWheelEvent::mDeltaMode r=smaug (dc8283ee70)
- Bug 1253041 - Don't apply user wheel prefs more than once. r=smaug (2386a9da14)
- Bug 1259664 part.5 Rename WidgetWheelEvent::customizedByUserPrefs to WidgetWheelEvent::mCustomizedByUserPrefs r=smaug (3b16757c1e)
- Bug 1258820 - making root scrollable element not scroll if mouse wheel is used on xul dropdown r=masayuki (58ab750981)
- Bug 1250050 Add a pref to disable supporting mouse wheel of windowless plugins on Windows r=smaug (1560cfcb35)
- Bug 1256162, use last drag target for dragexit event when comparing to a remote browser, r=smaug (2d03394037)
- Bug 1110030 - part1 - add flags:IsSynthesizedByTIP and InputMethodAppState in WidgetKeyboardEvent. r=masayuki (c8f7802ee0)
- Bug 1110030 - part2 - Prevent PostHandleKeyboardEvent if it's handling by IME. r=masayuki (29fab0d4fe)
- Bug 1110030 - part3 - Interface between PresShell and HardwareKeyHandler. r=masayuki, r=smaug (cb152d6b4b)
- Bug 1110030 - part4 - HardwareKeyHandler component. r=masayuki, r=smaug (9e372e8f19)
- Bug 1110030 - part5 - Expose KeyboardEventInit dictionary. r=masayuki, r=smaug (3c38bf2f37)
- Bug 1110030 - part6 - Expose DefaultPreventedBy*. r=masayuki, r=smaug (5c94c067be)
- Bug 1110030 - part7 - Interface between HardwareKeyHandler and Input Method App. r=masayuki, r=smaug (f33b7f5ad5)
- Bug 1244546 part 1 - Apply proper unit conversion for SynthesizeNativeMouseEvent. r=karlt (8596ff8068)
- Bug 1244546 part 2 - Align the center point for pointerlock to meet widget's requirement. r=smaug (c447b8c770)
- Bug 1259664 part.6 Rename WidgetWheelEvent::mayHaveMomentum to WidgetWheelEvent::mMayHaveMomentum r=smaug (ef95784e26)
- Bug 1259664 part.7 Rename WidgetWheelEvent::isMomentum to WidgetWheelEvent::mIsMomentum r=smaug (983c8dc9e3)
- Bug 1259664 part.8 Rename WidgetWheelEvent::scrollType to WidgetWheelEvent::mScrollType r=smaug (c6731aee2d)
- Bug 1259664 part.9 Rename WidgetWheelEvent::lineOrPageDeltaX to WidgetWheelEvent::mLineOrPageDeltaX r=smaug (9c6d3d7f6b)
- Bug 1259664 part.10 Rename WidgetWheelEvent::lineOrPageDeltaY to WidgetWheelEvent::mLineOrPageDeltaY r=smaug (8a0d549eee)
- Bug 1259664 part.11 Rename WidgetWheelEvent::overflowDeltaX to WidgetWheelEvent::mOverflowDeltaX r=smaug (6200509b12)
- Bug 1259664 part.12 Rename WidgetWheelEvent::overflowDeltaY to Widget¦WheelEvent::mOverflowDeltaY r=smaug (3827fc7cef)
- Bug 1259664 part.13 Reorder the definition of members of WidgetWheelEvent r=smaug (ea69305e49)
- Bug 1243628 - Allow for presshell to have been destroyed and disconnected from prescontext during event dispatch. r=dholbert (b98e7ed1a2)
- Bug 1256952, send a dragexit at remote process when leaving the remote frame, r=smaug (1037c6a425)
- Bug 1261818 - don't pass nsAutoCStrings into nsBaseWidget debugging methods; r=karlt (36b2b66795)
- Bug 1250560 - Fix crash in HandleTouchEvent. r=roc (9586890edf)
- Bug 1121468 - Go to NoActionState after receiving release on LongTapState. r=roc (4e05f9ad70)
- Bug 1248847 - Assert AccessibleCaretEventHub mRefCnt > 1 in all its entry points. r=mats (89ced3c3e2)
- Bug 1251915 - Correct AccessibleCaretEventHub coding style by clang-format. r=roc (12d5b507d7)
- Bug 1259668 Rename WidgetTouchEvent::touches to WidgetTouchEvent::mTouches r=smaug (9c53824e17)
- Bug 1259657 Rename WidgetGUIEvent::widget to WidgetGUIEvent::mWidget r=smaug (8707f57a28)
- Bug 1246477 - Fix carets not updated by scroll events in LongTapState. r=roc (ba6f12001c)
- Bug 1259662 part.1 Rename WidgetDragEvent::dataTransfer to WidgetDragEvent::mDataTransfer r=smaug (79ffebe3ae)
- Bug 1259662 part.2 Rename WidgetDragEvent::userCancelled to WidgetDragEvent::mUserCancelled r=smaug (4b7ee565e7)
- Bug 1241008 - Add preference to show or hide selection bars. r=mtseng (aefd4c430d)
- Bug 1242349 - Provide unique AccessibleCaret pref for JS selection changes, r=TYLin (e964cb16cf)
- Bug 1240917 - Do not show caret in empty input on Fennec. r=roc (d73c6bd94d)
- Bug 1121468 - Show carets when long-pressing on selection highlight. r=roc (bf134067de)
- Bug 1246064 - Support long press to show AccessibleCaret on empty input for Fennec. r=roc (90791443cb)
- Bug 1246918 - Fix carets missing after scrolling down in selection mode on Fennec. r=roc (bc0915ad70)
- Bug 1246918 - Handle PresShell gone after FlushLayout(). r=roc (b2f18c9a03)
- Bug 1251915 - Ignore handling eTouchCancel events. r=roc (a04c3ad8eb)
- Bug 1121468 - Use auto* to explicit declare 'self' as a pointer. r=roc (d83a6020e3)
- Bug 1251346 - Fennec should not generate touch events from mouse events. r=kats (d8077748fc)
- Bug 1259656 part.1 Rename WidgetEvent::refPoint to WidgetEvent::mRefPoint r=smaug (fa66825fc9)
- Bug 1259656 part.2 Rename WidgetEvent::lastRefPoint to WidgetEvent::mLastRefPoint r=smaug (c964d62185)
- Bug 1259656 part.3 Rename WidgetEvent::userType to WidgetEvent::mSpecifiedEventType r=smaug (218ae50355)
- Bug 1259656 part.4 Rename WidgetEvent::typeString to WidgetEvent::mSpecifiedEventTypeString r=smaug (0192c890c9)
- Bug 1259656 part.5 Rename WidgetEvent::target to WidgetEvent::mTarget r=smaug (031356f40d)
- Bug 1259656 part.6 Rename WidgetEvent::currentTarget to WidgetEvent::mCurrentTarget r=smaug (451810f6d3)
- Bug 1259656 part.7 Rename WidgetEvent::originalTarget to WidgetEvent::mOriginalTarget r=smaug (643379c9cb)
- Bug 1259673 rename from InternalClipboardEvent.clipboardData to InternalClipboardEvent.mClipboardData. r=masayuki (5ad3c180fe)
- Bug 1259674 Part 1 rename InternalFocusEvent::fromRaise to InternalFocusEvent::mFrameRaise r=masayuki (400f1ba6e9)
- Bug 1259674 Part 2 rename InternalFocusEvent::isRefocus to InternalFocusEvent::mIsRefocus r=masayuki (93b5799c31)
- Bug 1259674 Part 3 rename InternalFocusEvent::relatedTarget to InternalFocusEvent::mRelatedTarget r=masayuki (2382b8de82)
- Bug 1256589 part.7 Add PropagationStopped() to WidgetEvent r=smaug (09325f188d)
- Bug 1259663 - Clean up WidgetMouseScrollEvent. r=masayuki (48e1389e22)
- Bug 1263782 - Kill off the deprecated nsINode::IsInDoc(). r=baku (ae80ec21f7)
- Bug 1223751 - Assume all frames are visible in subdocuments of a top level content document that's assuming all frames are visible. r=tn (3b4f99b2fc)
- Bug 1223747 - Don't assume all frames are visible in XUL documents. r=tn (7f26104ff8)
- Bug 1180267 - Switch Fennec over to using the MobileViewportManager for computing the CSS viewport. r=snorp (b85a3a5fb7)
- put back some SPS stuff (960414c383)
- Bug 1151152 - Change behavior when pointer is dragged out of the document. r=smaug (64779386f0)
- Bug 1263787 - Kill off the deprecated nsINode::GetCrossShadowCurrentDoc. r=baku (ed490173ff)
- Bug 1261933 - Continue unlocking pointer even if the widget has gone. r=smaug (93a781887a)
- Bug 1259296 - Do not scroll snap on the main thread for wheel events handled by APZ. r=kats (f72a7fef66)
- Bug 1208371 - Pass parent window to DOMMediaStream through constructor. r=roc (1c2cc7e7c7)
- Bug 1208371 - Make AudioCaptureStream startable. r=padenot (15849ae011)
- Bug 1208371 - Move OnTracksAvailableCallback out of DOMMediaStream. r=roc (a772c949e0)
- Bug 1208371 - Remove unused MediaManager::NotifyMediaStreamTrackEnded. r=jib (9157aa056e)
- Bug 1208371 - Introduce MediaStreamTrack logs. r=roc,jib (72ff4d4c59)
- Bug 1208371 - Track original track in MediaStreamTrack clones. r=jib (39e9ae1200)
- Bug 1208371 - Un-nest MediaEngineSource::PhotoCallback. r=roc (b1e0b48012)
- Bug 1253333: Don't cause a second MediaManager to be created if there's a race with NotifyFinished r=jib (d22d6d01e5)
- Bug 1251357 - fix regression where last MediaStreamTrack.stop did not turn off the camera light. r=jesup (11b4880c02)
- Bug 1208371 - Add a MediaStreamTrackSource interface. r=roc (bb1880524b)
- Bug 1208371 - Add MediaStreamTrackSourceGetter interface. r=roc (c57a1c6a74)
- Bug 1208371 - Let MediaStreamTracks know their TrackID at the source. r=roc (62739d72bf)
- Bug 1259236 - throw NotSupportedError on pc.addTrack of track in constructed stream. r=bwc (8b828e80e4)
- Bug 1208371 - Let FindOwnedDOMTrack operate on input stream. r=roc (26c24ad3c7)
- Bug 1208371 - Add some MediaStreamTrack helper methods. r=roc (1627cd36f8)
- Bug 1208371 - Count the users of a MediaStream to ease Destroy() resonsibility. r=roc (613d446b39)
- bug 1223670 assert that connected streams have the same graph r=padenot (17ea987392)
- Bug 1208371 - Add convenience method for checking if TrackID is explicit. r=roc (32c9d5fe45)
- Bug 1208371 - Allow MediaInputPorts mapped to a destination TrackID. r=roc (71d3c66c4d)
- Bug 1208371 - Remove obsolete SetTrackEnabled() from DOMMediaStream r=roc (cbcf54a342)
- Bug 1208371 - Add MediaStreamTrack::Graph(). r=jib (044b348267)
- Bug 1208371 - Add DOMMediaStream::GetTrackById/GetOwnedTrackById. r=jib (6f58360808)
- Bug 1208371 - Add a generic PrincipalChangeObserver interface. r=mt (79dfb91a89)
- Bug 1208371 - Turn DOMMediaStream::PrincipalChangeObserver into PrincipalChangeObserver<DOMMediaStream>. r=mt (41fdd835de)
- Bug 1208371 - Add principal APIs to MediaStreamTrack. r=mt (a8aab0e0d9)
- Bug 1208371 - Create MediaStreamTrackSource::Sink that forwards principal changes from the source. r=mt (d9fefd4e3c)
- Bug 1208371 - Let DOMMediaStream base its principal on the tracks it contains. r=mt (0c046c7c20)
- Bug 1208371 - Add an interface DecoderPrincipalChangeObserver to HTMLMediaElement. r=roc (ecde0ddae0)
- Bug 1208371 - Make HTMLMediaElement::CaptureStream pass its principal to MediaStreamTrack. r=mt (03ccd4ab12)
- Bug 1208371 - Make MediaManager pass its principal to MediaStreamTrack for gUM. r=mt (5a4b1eb0bc)
- Bug 1208371 - Make ImageCapture listen to principal changes of MediaStreamTrack instead. r=mt (d29fc5c36e)
- Bug 1208371 - Break PCImpl::SetRemoteDescription into smaller pieces. r=mt,bwc (e4337cc413)
- Bug 1208371 - Make PeerConnectionImpl pass its principal to MediaStreamTrack through a new RemoteTrackSource. r=mt (9a7c744a19)
- Bug 1208371 - Make remaining DOMMediaStream principal sources use MediaStreamTrack. r=mt (b30919c681)
- Bug 1208371 - Remove some no longer necessary principal methods on DOMMediaStream. r=mt (5279935d2b)
- Bug 1208371 - Add a MediaStreamTrackListener to MediaStreamGraph. r=roc (5d7048793e)
- Bug 1208371 - Move ImageCapture to a MediaStreamTrackListener. r=roc (98d081b034)
- Bug 1208371 - Make it possible to look up stream id by track in PeerConnectionImpl. r=jib (c65d07cb55)
- Bug 1208371 - Fix DOMMediaStream::OwnsTrack. r=roc (092dad3654)
- Bug 1208371 - Remove MediaStreamTrack::GetStream. r=jib (a78f873d89)
- Bug 1208371 - Route ApplyConstraints through MediaStreamTrackSource. r=jib (32dfd76245)
- Bug 1208371 - Kill nsDOMUserMediaStream with fire. r=jib (9550ad61be)
- Bug 1208371 - Make it possible to block tracks in a MediaInputPort initally. r=roc (39fdd2322f)
- Bug 1208371 - Implement MediaStreamTrack::Clone(). r=smaug,jib,roc (1995b87f10)
- Bug 1208371 - Implement DOMMediaStream::Clone() r=smaug,jib,roc (d445dffd30)
- Bug 1208371 - Various cleanups in DOMMediaStream/MediaStreamTrack. r=jib (a174781a1b)
- Bug 1208371 - Forward input stream and track id on regular track changes for union streams. r=roc (36ed98f3e4)
- Bug 1208371 - Move track.stop() helpers to MediaStreamPlayback. r=jib (123bcd8192)
- Bug 1240478 - Add test for video size on 'loadedmetadata'. r=jesup,jib Bug 1208371 - Test DOMMediaStream::Clone(). r=jib (5ff908fcad)
- Bug 1208371 - Test DOMMediaStream::Clone(). r=jib (445556e6b5)
- Bug 1208371 - Rename CreateOwnDOMTrack/CreateClonedDOMTrack to CreateDOMTrack/CloneDOMTrack. r=jib (41c71b073c)
- Bug 1208371 - Resolve ambiguous symbol MediaStreamTrack. r=bwc (f998354d96)
- Bug 1208371 - Test MediaStreamTrack::Clone(). r=jib (34865902b0)
- Bug 1208371 - Add CORSMode methods to MediaStreamTrack. r=mt (2244b06567)
- Bug 1208371 - Add convenience method for forwarding a track to an MSG-stream. r=roc (f21675220b)
- bug 1223670 throw not supported when creating a node from a stream with different channel r=baku (55e1d67356)
- Bug 1208371 - Lock MediaStreamAudioSourceNode onto the first AudioStreamTrack in mInputStream at time of construction. r=mt,padenot (77640fa752)
- Bug 1208371 - Move HTMLMediaElement::CaptureStream to forward CORSMode changes through MediaStreamTrackSource. r=mt (aa533e8945)
- Bug 1208371 - Remove CORSMode methods from DOMMediaStream. r=mt (41447ccea1)
- Bug 1208371 - Change HTMLMediaElement video sinks to check principal for video only tracks. r=mt (b8168de792)
- Bug 1208371 - Remove ref counting from DOMMediaStream::TrackListener. r=roc (fe275597ca)
- Bug 1208371 - Update tests to accomodate the fact that MediaStreamAudioSourceNodes lock onto the first audio track. r=padenot (ee97d9f274)
- bug 916387 remove workarounds in tests r=padenot (0285f2b98a)
- Bug 1208371 - Modify WebAudio source nodes tests to wait for tracks befoure measuring nr of samples. r=padenot (bb3afcdd12)
- Bug 1208371 - Make PeerIdentity RefCounted. r=mt (74320ca464)
- Bug 1208371 - Add GetPeerIdentity() to MediaStreamTrackSource. r=mt (77bffa988f)
- Bug 1208371 - Add PeerIdentity to LocalTrackSource for gUM tracks. r=mt (4b4163b66d)
- Bug 1208371 - Move PeerConnection to use PeerIdentity on MediaStreamTrack. r=mt (30b5ab4b5d)
- Bug 1208371 - Remove PeerIdentity from DOMMediaStream. r=mt (0d6b66614e)
- Bug 1208371 - Add convenience method for checking track forwarding to MediaStreamTrack. r=roc (d9eb5210e0)
- Bug 1208371 - Remove test_mediarecorder_record_nosrc.html. r=roc (5d7b83b51c)
- Bug 1208371 - Inline MediaRecorder::Session::SetupStreams. r=roc (1f8463266e)
- Bug 1153690 - Release the Session and MediaRecorder objects correctly if there is no ExtractRunnable running. r=roc (424117e697)
- Bug 1257318: Move MediaRecorder to use DirectListeners wherever possible. r=roc (1d6336641b)
- Bug 1208371 - Check principal per track instead of per stream in MediaRecorder. r=roc (946080d5e7)
- Bug 1219711 - Add mochitest for track disabling over a peer connection. r=jib (ad345e05e2)
- Bug 1166832 - Add test to verify audio (using AudioStreamAnalyser) after renegotiation. r=bwc (ad11535ee3)
- Bug 1250934: Make tests use the correct audio frequency when fake devices are used r=drno (1b8cb18b8f)
- Bug 1182426 - Test that we can record CanvasCaptureMediaStreams. r=roc (e65a5dce61)
- Bug 1219711 - Refactor captureStream_common.js to accept generic pixel testing method. r=jib (e55194c8f5)
- Bug 1223696 - Make canvas captureStream helper resilient to exceptions when there's no video. r=roc (215711b89d)
- Bug 1223696. Don't destroy VideoFrameContainer when we reach MetadataLoaded without a video track. r=jwwang (2c83a23b24)
- Bug 1166832 - Add test for canvas capture on multiple streams. r=bwc (592db27fc4)
- Bug 1224029: ensure video elements that may be disabled (black) are scaled r=mattwoodrow (bfef6af17d)
- Bug 1208371 - Simplify track disabling test code. r=jib (771d560f40)
- Bug 1208371 - Test disabling track across peerconnection not affecting clones. r=jib (31913dd9e6)
- Bug 1208371 - Add DirectTrackLister to MediaStreamGraph and MediaStreamTrack. r=roc,jesup (ae86375502)
- Bug 1208371 - Switch MediaPipeline to use direct listeners on tracks. r=jesup,bwc (aa32ce43e1)
- Bug 1208371 - Let PeerConnection consume principals from tracks instead of streams. r=mt (825d6775d0)
- Bug 1257318: Make recorder.pause()/resume() work with DirectListeners r=padenot (65ff6bff4e)
- Bug 1208371 - Move FindTrack from SourceMediaStream to MediaStream. r=roc (5725a863ae)
- Bug 1208371 - Switch MediaStreamTrack to enable/disable tracks on owned stream. r=jesup (ea7445369d)
- Bug 1208371 - Make GraphDriver callback log calls verbose. r=padenot (a502ceea55)
- Bug 1208371 - Do not require DOMLocalMediaStream for gUM (audioCapture). r=jib (c27ee1c308)
- Bug 1208371 - Add a track getter to gUM for fake tracks. r=jib (4958d2b392)
- Bug 1208371 - Remove unnecessary fakeTracks:true from test_streams_individual_pause.html. r=jib (2437b932b2)
- Bug 1208371 - Remove debug canvas from addTrackRemoveTrack test to help android perf. r=padenot (38b1ad91e8)
- Bug 1208371 - Remove debug canvas from peerConnection_trackDisabling test to help android perf. r=padenot (a8b5a62ad6)
- Bug 1208371 - Enable test_peerConnection_trackDisabling.html on android 4.3 debug. r=jib (19e14785f3)
- Bug 1208371 - Remove debug canvas from mediaStreamConstructors test to help android perf. r=padenot (b7e3280b68)
- Bug 1208371 - Add disconnect method to test helper AudioStreamAnalyser to improve performance on slow devices. r=padenot (2a18d693a7)
- Bug 1208371 - Guard LoadManagerSingleton with a WeakPtr. r=pkerr (0f6d425cf4)
- Bug 1208371 - Clear output canvas on each drawImage(). r=jib (45731c0c9a)
- Bug 1208371 - Wait for "loadedmetadata" before setting up nodes in test_mediaElementAudioSourceNodeCrossOrigin.html. r=padenot (8e9398fe88)
- Bug 1181051 - Remove BOM from dom/media/test/test*.html. r=kinetik (c98b2c9b96)
- Bug 1070110 - Ensure the testcase will receive at least 2 blob. r=jwwang (028cb09b08)
- Bug 1208371 - Improve reliability of test_mediarecorder_record_timeslice.html. r=jwwang (c89bd3fe0b)
- Bug 1251494 - Remove remaining references to MOZILLA_XPCOMRT_API from dom. r=jesup (5b8e7ef603)
- Bug 1234230: Don't pass null images for video encoding, and don't reencode the same image r=roc (eb09c07d8d)
- Bug 1258567 - per comment 3, ensure volume is only applied once to the AudioSegment. r=kinetik. (a7e184e483)
- Bug 1250934: Modify MediaEngine shutdown to allow neutering the AudioDataListener r=padenot (70f4831508)
- Bug 1208371 - Add PrincipalHandle to MediaChunks. r=mt,jesup (0662c26a97)
- Bug 1208371 - Add NotifyPrincipalHandleChanged to MediaStreamTrackListener. r=mt,jesup (54d896b1ff)
- Bug 1208371 - Add a PrincipalHandleListener to MediaStreamTrack. r=mt,jesup (9b4d2e0996)
- Bug 1256520 - use SyncRunnable to create DecodedStreamData synchronously to ensure the creation and destruction of DecodedStreamData happen in order. r=kikuo. (7d28eaebba)
- Bug 1237482 - Remove MediaDecoderStateMachine::mStreamSink. r=kikuo. (609efed70f)
- Bug 1208371 - Hook up DecodedStream with PrincipalHandle. r=mt,jwwang (9290efb216)
- Bug 1208371 - Hook up MediaPipeline with PrincipalHandle. r=mt,bwc (b00ed70ddc)
- Bug 1208371 - Ensure DOMMediaStream principals reflect what could reside in their playback streams. r=mt,jesup (bd3e32ef16)
- Bug 1208371 - Ensure a media element's ImageContainer is protected when playing a stream. r=mt,jesup (7914faa5dc)
- Bug 1208371 - Forward declare MediaStreamTrack in MediaPipeline.h. r=bwc (4c88b79137)
- Bug 1208371 - Clean up unnecessary virtuals in MediaPipeline. r=bwc (b7112f4b34)
- Bug 1208371 - Move MediaStreamTrack's label to MediaStreamTrackSource. r=jib (045bfcc738)
- Bug 1259590 - Remove B2G ACL code. r=khuey (e13223c0f6)
- Bug 1265452 followup - Make mGlyphExtentsArray an array of UniquePtr. r=jrmuizel (d12ce46244)
- Bug 1250540 - have media.navigator.permission.disabled no longer bypass media.getusermedia.screensharing.allowed_domains. r=jesup (e55aa02580)
- Bug 1247806 - Fix HSTS redirect check in WebSocketChannel, r=mcmanus (4e44becf9e)
- Bug 1251530: Use 'MakeUnique' instead of 'new' to populate UniquePtr variable in AddCSSValuePair. r=jwatt (286bf2289a)
- Bug 1264787: Make nsCSSValue's AdoptListValue & AdoptPairListValue methods take a UniquePtr. r=heycam (0f7dc7164f)
- Bug 1151243 part 3 - [css-grid] Add a generic nsHTMLReflowState::STATIC_POS_IS_CB_ORIGIN flag to place the static-position at the CB origin, and make nsAbsoluteContainingBlock use it in Grid containers where the placeholder is a child too. r=dholbert (7bde498cc4)
- Bug 1151243 part 4 - Some code cleanup in nsHTMLReflowState::CalculateHypotheticalPosition, and make a few methods 'const' (idempotent patch). r=dholbert (e430f9b99d)
- Bug 1250540 - fix string-parsing regression that made screen share whitelisting stop working. r=jesup (f8af7c3053)
- Bug 1265641: Move AEC tail length and delay-agnostic settings to getUserMedia r=padenot (3feaf716ee)
- Bug 950936 - Introduce ui.popup.disable_autohide pref to ease debugging popups. r=neil (bc28dcd531)
- bug 1211892 read -unico-border-gradient of early Unico versions for ThreeDHighlight and ThreeDShadow r=acomminos (39c83b1865)
- bug 1257695 disable GTK3 deprecation warnings in widget/gtk r=acomminos (1ad2ba0271)
- Bug 1258989 - Switch gtk3drawing to a C++ file, r=karlt (55b8ea3cdf)
- Bug 1259433 - remove gtk_widget_style_get() from gtk3drawing, r=karlt a=kwierso (a9fc049140)
- Bug 1266680 - Rename BlockTrackId to BlockSourceTrackId. r=jesup (5066bbf870)
- Bug 1262808: Refactor |PrincipalID| to |PrincipalHandle| in Gonk media code, r=rjesup (49ec99e852)
- Bug 1144096 part 1 - [css-grid] Refactor nsGridContainerFrame state and methods. r=dholbert (502fb7d424)
- Bug 1144096 part 2 - [css-grid] Make GridItemInfo::mFrame available also in non-DEBUG builds since we'll need it to support fragmentation. r=dholbert (fe0b396741)
- Bug 1144096 part 3 - [css-grid] Remove CellMap::ClearOccupied() since it's not needed anymore. r=dholbert (7aeacdccab)
- Bug 1144096 part 4 - [css-grid] Move more local nsGridContainerFrame classes into .cpp file. r=dholbert (896fc410ac)
- Bug 1144096 part 5 - [css-grid] Create a couple of Grid container frame bits. r=dholbert (abd007fd90)
- Bug 1144096 part 6 - [css-grid] Add support for creating Grid container continuations and deal with overflow containers. r=dholbert (3f02728f24)
- Bug 1144096 part 7 - [css-grid] Don't create PageBreakFrames inside a Grid container. The container will handle forced breaks on its items. r=dholbert (5a0c64369c)
- Bug 1144096 part 8 - [css-grid] Add a new state flag, eBreakBefore, to record where breaks occur between tracks. r=dholbert (5f5d8761c6)
- Bug 1144096 part 9 - [css-grid] Create a SharedGridData object owned by the first-in-flow Grid container to share state between its continuations. r=dholbert (7e510f0bf0)
- Bug 1144096 part 10 - [css-grid] Add a few helper methods to do a break before a row, and resize a row. r=dholbert (d3fec771a6)
- Bug 1144096 part 11 - [css-grid] Add a GetNearestFragmentainer() method that collects some data from the nearest enclosing fragmentainer needed for fragmentation. r=dholbert (20d4736a1c)
- Bug 1144096 part 12 - [css-grid] Collect and merge child frames we need for reflow. r=dholbert (3b5c619f34)
- Bug 1144096 part 13 - [css-grid] Refactor ReflowChildren() by separating out the code that reflows normal flow children (grid items and placeholders) into a new method ReflowInFlowChild(). r=dholbert (ba8a1edc7b)
- Bug 1144096 part 14 - [css-grid] Make ReflowInFlowChild() deal with a constrained available block-size. r=dholbert (292df4167a)
- Bug 1144096 part 15 - [css-grid] Compute our pre-reflow logical skip sides and cache the result of ComputedLogicalBorderPadding() with that applied. r=dholbert (0c7f59389e)
- Bug 1144096 part 16 - [css-grid] Implement fragmentation. r=dholbert (af8b8aac41)
- Bug 1144096 part 17 - [css-grid] Add helper methods that add a sorted list of child frames to the Overflow and ExcessOverflowContainers child lists. r=dholbert (a6c80c9e9c)
- Bug 1144096 part 18 - [css-grid] Fix a couple of bugs in how we handle child existing continuations when pushing/pulling children. r=dholbert (b8a62fab65)
- Bug 1144096 part 19 - [css-grid] Sanity check the initial child lists we get from the frame constructor (DEBUG only). r=dholbert (f56955c3ec)
- Bug 1144096 part 20 - [css-grid] Sanity check our child lists before starting a Reflow (DEBUG only). r=dholbert (d89c771238)
- Bug 1144096 part 21 - [css-grid] Deal with dynamically inserted/appended/removed child frames. r=dholbert (a07ecc06e2)
- Bug 1144096 part 22 - [css-grid] Check NS_INLINE_IS_BREAK_BEFORE before checking other completion status. r=dholbert (731dcd0ac5)
- Bug 1144096 part 23 - [css-grid] A grid container fragment that is an overflow container can't be INCOMPLETE, only OVERFLOW_INCOMPLETE and it should always have zero BSize. r=dholbert (afcd6c1741)
- Bug 1144096 part 24 - [css-grid] Move the child frame merging code at the start of ReflowOverflowContainerChildren into a new method: DrainExcessOverflowContainersList. Make both take a param so that we can override how the OC child lists are merged together (normally just an Append; MergeSortedFrameLists for Grid). r=dholbert (95323b0a56)
- Bug 1144096 part 25 - [css-grid] Enable fragmentation to occur by reporting our actual reflow status. r=dholbert (767c389238)
- Bug 1144096 part 26 - [css-grid] Fragmentation reftests. (07e62f8fd6)
- Bug 1244006 - Use const instead of MOZ_CONSTEXPR to avoid startup crash; r=dbaron (39b0bb32fb)
- Bug 1053986 - Fix comment referring to a GetBorder method that no longer exists. r=dholbert (2cde22a42c)
- Bug 1264784 - part 1 - call nsIFrame::{GetScreenRect,GetView} instead of their *External counterparts; r=dholbert (a2cd84b7a2)
- Bug 963238: Support isTypeSupported() in MediaRecorder, and throw on invalid mimetypes at construction r=cpearce,khuey (5a3709e45f)
- Bug 963238: Fix compilation errors. r=me (09b541337f)
- Bug 1157654 - 1. Do not call MediaRecorder.stop immediately after MediaRecorder.start, because we want to receive an onerror callback. 2. Make the NofityError async. r=roc (ad56918042)
- Bug 1128448 - 1. Close the pref media.encoder.omx.enabled for newer android version. 2. Modify testcase and MR to eusure no timing issue. r=roc (9dfff44a2c)
- Bug 1197669 - Part3 - Ensure the start event comes before any blobs. r=jwwang (d4e950fa49)
- Bug 1225327 - fix |mEncoder| null-dereference. r=jwwang (5ffce18e33)
- Bug 1260702: Don't crash on input sources from WebAudio in MediaRecorder r=padenot (28bcb80a03)
- Bug 1018299 - Throw security error if principal check fails in MediaRecorder::Start(). r=jib, r=mt (1130168c8d)
- Bug 1261007 - Part 1 - Force to send video sample into encoder if we got the same video sample more than 1 seconds. Enable testcases. r=jolin (5698b39b45)
- Bug 1182426 - Test that changing video resolution of a recorded stream throws an error. r=roc (edaf49f2cf)
- Bug 1261007 - Part 2 - fix test_mediarecorder_webm_support. r=rillian (16ef931949)
- Bug 1250054. Part 1 - implement MediaDecoderReaderWrapper. r=jya. (4b78b4335d)
- Bug 1242841 - Make MDSM::mDecodedAudioEndTime zero-based. r=kikuo. (c6c756d554)
- Bug 1242843 - Make MDSM::mDecodedVideoEndTime zero-based. r=kikuo. (c194178323)
- Bug 1242783. Part 1 - per comment 11, increase mAmpleAudioThresholdUsecs to 2s to avoid audio underrun when BT is connected. r=kinetik. (49567f587e)
- Bug 1242783. Part 2 - per comment 12, buffer only 1s when audio is captured. r=kinetik. (e0f0cd9826)
- Bug 1230641: P1. Use UYVY (YUV422) format in decoders. r=mattwoodrow (17d4de398c)
- Bug 1230641: P2. Increase the video queue size on mac. r=cpearce (e575c85f44)
- Bug 1230641 - Make our NSOpenGLContext opaque when in fullscreen mode. r=mstange (51da9e1e70)
- Bug 1257094 - Remove MediaDecoderStateMachine::DECODER_STATE_DECODING_NONE. r=cpearce. (f0993582c7)
- Bug 1252344. Part 1 - remove unnecessary checks from MediaDecoderStateMachine::NeedToDecodeVideo(). r=bechen. (1c9b7aadf4)
- Bug 1252344. Part 2 - remove unnecessary checks from MediaDecoderStateMachine::NeedToDecodeAudio(). r=bechen. (6896d7d1f6)
- Bug 1252360 - remove some check from NeedToDecodeAudio(). r=bechen. (1c064a9985)
- Bug 1252762 - Decode at most one audio/video sample before finishing seeking. r=cpearce. (73ec7691df)
- Bug 1253928 - adjust the time passed to RequestVideoData() by the start time to avoid incorrectly skipping key frames. r=jya. (1bc203d8e6)
- Bug 1252766 - Remove MediaDecoderStateMachine::mDecodeToSeekTarget which is never read. r=kaku. (aac1fe8018)
- Bug 1251460 - MDSM now waits on a promise to enqueue first frame loaded. r=jya (0e7e67e006)
- Bug 1257013 - Part 3: Stop checking for corrupt frames in MediaDecoderStateMachine since we no longer produce them. r=ajones (fbbbabafbb)
2024-04-24 23:53:16 +08:00

2972 lines
101 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef XP_WIN
// Include Windows headers required for enabling high precision timers.
#include "windows.h"
#include "mmsystem.h"
#endif
#include <algorithm>
#include <stdint.h>
#include "gfx2DGlue.h"
#include "mediasink/AudioSinkWrapper.h"
#include "mediasink/DecodedAudioDataSink.h"
#include "mediasink/DecodedStream.h"
#include "mediasink/OutputStreamManager.h"
#include "mediasink/VideoSink.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Logging.h"
#include "mozilla/mozalloc.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Preferences.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/TaskQueue.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsIEventTarget.h"
#include "nsITimer.h"
#include "nsPrintfCString.h"
#include "nsTArray.h"
#include "nsDeque.h"
#include "prenv.h"
#include "AudioSegment.h"
#include "DOMMediaStream.h"
#include "ImageContainer.h"
#include "MediaDecoder.h"
#include "MediaDecoderReader.h"
#include "MediaDecoderStateMachine.h"
#include "MediaShutdownManager.h"
#include "MediaTimer.h"
#include "TimeUnits.h"
#include "VideoSegment.h"
#include "VideoUtils.h"
#include "gfxPrefs.h"
namespace mozilla {
using namespace mozilla::dom;
using namespace mozilla::layers;
using namespace mozilla::media;
#define NS_DispatchToMainThread(...) CompileError_UseAbstractThreadDispatchInstead
// avoid redefined macro in unified build
#undef LOG
#undef DECODER_LOG
#undef VERBOSE_LOG
#define LOG(m, l, x, ...) \
MOZ_LOG(m, l, ("Decoder=%p " x, mDecoderID, ##__VA_ARGS__))
#define DECODER_LOG(x, ...) \
LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
#define VERBOSE_LOG(x, ...) \
LOG(gMediaDecoderLog, LogLevel::Verbose, x, ##__VA_ARGS__)
#define SAMPLE_LOG(x, ...) \
LOG(gMediaSampleLog, LogLevel::Debug, x, ##__VA_ARGS__)
// Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__
// when __VA_ARGS__ expands to nothing. This is a workaround for it.
#define DECODER_WARN_HELPER(a, b) NS_WARNING b
#define DECODER_WARN(x, ...) \
DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get()))
// Certain constants get stored as member variables and then adjusted by various
// scale factors on a per-decoder basis. We want to make sure to avoid using these
// constants directly, so we put them in a namespace.
namespace detail {
// If audio queue has less than this many usecs of decoded audio, we won't risk
// trying to decode the video, we'll skip decoding video up to the next
// keyframe. We may increase this value for an individual decoder if we
// encounter video frames which take a long time to decode.
static const uint32_t LOW_AUDIO_USECS = 300000;
// If more than this many usecs of decoded audio is queued, we'll hold off
// decoding more audio. If we increase the low audio threshold (see
// LOW_AUDIO_USECS above) we'll also increase this value to ensure it's not
// less than the low audio threshold.
static const int64_t AMPLE_AUDIO_USECS = 2000000;
} // namespace detail
// If we have fewer than LOW_VIDEO_FRAMES decoded frames, and
// we're not "prerolling video", we'll skip the video up to the next keyframe
// which is at or after the current playback position.
static const uint32_t LOW_VIDEO_FRAMES = 2;
// Threshold in usecs that used to check if we are low on decoded video.
// If the last video frame's end time |mDecodedVideoEndTime| is more than
// |LOW_VIDEO_THRESHOLD_USECS*mPlaybackRate| after the current clock in
// Advanceframe(), the video decode is lagging, and we skip to next keyframe.
static const int32_t LOW_VIDEO_THRESHOLD_USECS = 60000;
// Arbitrary "frame duration" when playing only audio.
static const int AUDIO_DURATION_USECS = 40000;
// If we increase our "low audio threshold" (see LOW_AUDIO_USECS above), we
// use this as a factor in all our calculations. Increasing this will cause
// us to be more likely to increase our low audio threshold, and to
// increase it by more.
static const int THRESHOLD_FACTOR = 2;
// When the continuous silent data is over this threshold, means the a/v does
// not produce any sound. This time is decided by UX suggestion, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1235612#c18
static const uint32_t SILENT_DATA_THRESHOLD_USECS = 10000000;
namespace detail {
// If we have less than this much undecoded data available, we'll consider
// ourselves to be running low on undecoded data. We determine how much
// undecoded data we have remaining using the reader's GetBuffered()
// implementation.
static const int64_t LOW_DATA_THRESHOLD_USECS = 5000000;
// LOW_DATA_THRESHOLD_USECS needs to be greater than AMPLE_AUDIO_USECS, otherwise
// the skip-to-keyframe logic can activate when we're running low on data.
static_assert(LOW_DATA_THRESHOLD_USECS > AMPLE_AUDIO_USECS,
"LOW_DATA_THRESHOLD_USECS is too small");
} // namespace detail
// Amount of excess usecs of data to add in to the "should we buffer" calculation.
static const uint32_t EXHAUSTED_DATA_MARGIN_USECS = 100000;
// If we enter buffering within QUICK_BUFFER_THRESHOLD_USECS seconds of starting
// decoding, we'll enter "quick buffering" mode, which exits a lot sooner than
// normal buffering mode. This exists so that if the decode-ahead exhausts the
// downloaded data while decode/playback is just starting up (for example
// after a seek while the media is still playing, or when playing a media
// as soon as it's load started), we won't necessarily stop for 30s and wait
// for buffering. We may actually be able to playback in this case, so exit
// buffering early and try to play. If it turns out we can't play, we'll fall
// back to buffering normally.
static const uint32_t QUICK_BUFFER_THRESHOLD_USECS = 2000000;
namespace detail {
// If we're quick buffering, we'll remain in buffering mode while we have less than
// QUICK_BUFFERING_LOW_DATA_USECS of decoded data available.
static const uint32_t QUICK_BUFFERING_LOW_DATA_USECS = 1000000;
// If QUICK_BUFFERING_LOW_DATA_USECS is > AMPLE_AUDIO_USECS, we won't exit
// quick buffering in a timely fashion, as the decode pauses when it
// reaches AMPLE_AUDIO_USECS decoded data, and thus we'll never reach
// QUICK_BUFFERING_LOW_DATA_USECS.
static_assert(QUICK_BUFFERING_LOW_DATA_USECS <= AMPLE_AUDIO_USECS,
"QUICK_BUFFERING_LOW_DATA_USECS is too large");
} // namespace detail
static TimeDuration UsecsToDuration(int64_t aUsecs) {
return TimeDuration::FromMicroseconds(aUsecs);
}
static int64_t DurationToUsecs(TimeDuration aDuration) {
return static_cast<int64_t>(aDuration.ToSeconds() * USECS_PER_S);
}
static const uint32_t MIN_VIDEO_QUEUE_SIZE = 3;
static const uint32_t MAX_VIDEO_QUEUE_SIZE = 10;
#ifdef MOZ_APPLEMEDIA
static const uint32_t HW_VIDEO_QUEUE_SIZE = 10;
#else
static const uint32_t HW_VIDEO_QUEUE_SIZE = 3;
#endif
static const uint32_t VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE = 9999;
static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
static uint32_t sVideoQueueHWAccelSize = HW_VIDEO_QUEUE_SIZE;
static uint32_t sVideoQueueSendToCompositorSize = VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE;
static void InitVideoQueuePrefs() {
MOZ_ASSERT(NS_IsMainThread());
static bool sPrefInit = false;
if (!sPrefInit) {
sPrefInit = true;
sVideoQueueDefaultSize = Preferences::GetUint(
"media.video-queue.default-size", MAX_VIDEO_QUEUE_SIZE);
sVideoQueueHWAccelSize = Preferences::GetUint(
"media.video-queue.hw-accel-size", HW_VIDEO_QUEUE_SIZE);
sVideoQueueSendToCompositorSize = Preferences::GetUint(
"media.video-queue.send-to-compositor-size", VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE);
}
}
MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
MediaDecoderReader* aReader,
bool aRealTime) :
mDecoderID(aDecoder),
mFrameStats(&aDecoder->GetFrameStatistics()),
mVideoFrameContainer(aDecoder->GetVideoFrameContainer()),
mAudioChannel(aDecoder->GetAudioChannel()),
mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ true)),
mWatchManager(this, mTaskQueue),
mRealTime(aRealTime),
mDispatchedStateMachine(false),
mDelayedScheduler(mTaskQueue),
mState(DECODER_STATE_DECODING_METADATA, "MediaDecoderStateMachine::mState"),
mCurrentFrameID(0),
mObservedDuration(TimeUnit(), "MediaDecoderStateMachine::mObservedDuration"),
mFragmentEndTime(-1),
mReader(aReader),
mDecodedAudioEndTime(0),
mDecodedVideoEndTime(0),
mPlaybackRate(1.0),
mLowAudioThresholdUsecs(detail::LOW_AUDIO_USECS),
mAmpleAudioThresholdUsecs(detail::AMPLE_AUDIO_USECS),
mQuickBufferingLowDataThresholdUsecs(detail::QUICK_BUFFERING_LOW_DATA_USECS),
mIsAudioPrerolling(false),
mIsVideoPrerolling(false),
mAudioCaptured(false),
mAudioCompleted(false, "MediaDecoderStateMachine::mAudioCompleted"),
mVideoCompleted(false, "MediaDecoderStateMachine::mVideoCompleted"),
mNotifyMetadataBeforeFirstFrame(false),
mDispatchedEventToDecode(false),
mQuickBuffering(false),
mMinimizePreroll(false),
mDecodeThreadWaiting(false),
mDropAudioUntilNextDiscontinuity(false),
mDropVideoUntilNextDiscontinuity(false),
mCurrentTimeBeforeSeek(0),
mDecodingFirstFrame(true),
mSentLoadedMetadataEvent(false),
mSentFirstFrameLoadedEvent(false),
mSentPlaybackEndedEvent(false),
mOutputStreamManager(new OutputStreamManager()),
mResource(aDecoder->GetResource()),
mAudioOffloading(false),
mSilentDataDuration(0),
mBuffered(mTaskQueue, TimeIntervals(),
"MediaDecoderStateMachine::mBuffered (Mirror)"),
mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
"MediaDecoderStateMachine::mEstimatedDuration (Mirror)"),
mExplicitDuration(mTaskQueue, Maybe<double>(),
"MediaDecoderStateMachine::mExplicitDuration (Mirror)"),
mPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING,
"MediaDecoderStateMachine::mPlayState (Mirror)"),
mNextPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED,
"MediaDecoderStateMachine::mNextPlayState (Mirror)"),
mLogicallySeeking(mTaskQueue, false,
"MediaDecoderStateMachine::mLogicallySeeking (Mirror)"),
mVolume(mTaskQueue, 1.0, "MediaDecoderStateMachine::mVolume (Mirror)"),
mLogicalPlaybackRate(mTaskQueue, 1.0,
"MediaDecoderStateMachine::mLogicalPlaybackRate (Mirror)"),
mPreservesPitch(mTaskQueue, true,
"MediaDecoderStateMachine::mPreservesPitch (Mirror)"),
mSameOriginMedia(mTaskQueue, false,
"MediaDecoderStateMachine::mSameOriginMedia (Mirror)"),
mMediaPrincipalHandle(mTaskQueue, PRINCIPAL_HANDLE_NONE,
"MediaDecoderStateMachine::mMediaPrincipalHandle (Mirror)"),
mPlaybackBytesPerSecond(mTaskQueue, 0.0,
"MediaDecoderStateMachine::mPlaybackBytesPerSecond (Mirror)"),
mPlaybackRateReliable(mTaskQueue, true,
"MediaDecoderStateMachine::mPlaybackRateReliable (Mirror)"),
mDecoderPosition(mTaskQueue, 0,
"MediaDecoderStateMachine::mDecoderPosition (Mirror)"),
mMediaSeekable(mTaskQueue, true,
"MediaDecoderStateMachine::mMediaSeekable (Mirror)"),
mDuration(mTaskQueue, NullableTimeUnit(),
"MediaDecoderStateMachine::mDuration (Canonical"),
mIsShutdown(mTaskQueue, false,
"MediaDecoderStateMachine::mIsShutdown (Canonical)"),
mNextFrameStatus(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
"MediaDecoderStateMachine::mNextFrameStatus (Canonical)"),
mCurrentPosition(mTaskQueue, 0,
"MediaDecoderStateMachine::mCurrentPosition (Canonical)"),
mPlaybackOffset(mTaskQueue, 0,
"MediaDecoderStateMachine::mPlaybackOffset (Canonical)"),
mIsAudioDataAudible(mTaskQueue, false,
"MediaDecoderStateMachine::mIsAudioDataAudible (Canonical)")
{
MOZ_COUNT_CTOR(MediaDecoderStateMachine);
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Dispatch initialization that needs to happen on that task queue.
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<RefPtr<MediaDecoder>>(
this, &MediaDecoderStateMachine::InitializationTask, aDecoder);
mTaskQueue->Dispatch(r.forget());
InitVideoQueuePrefs();
mBufferingWait = IsRealTime() ? 0 : 15;
mLowDataThresholdUsecs = IsRealTime() ? 0 : detail::LOW_DATA_THRESHOLD_USECS;
#ifdef XP_WIN
// Ensure high precision timers are enabled on Windows, otherwise the state
// machine isn't woken up at reliable intervals to set the next frame,
// and we drop frames while painting. Note that multiple calls to this
// function per-process is OK, provided each call is matched by a corresponding
// timeEndPeriod() call.
timeBeginPeriod(1);
#endif
mAudioQueueListener = AudioQueue().PopEvent().Connect(
mTaskQueue, this, &MediaDecoderStateMachine::OnAudioPopped);
mVideoQueueListener = VideoQueue().PopEvent().Connect(
mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread());
mMediaSink = CreateMediaSink(mAudioCaptured);
#ifdef MOZ_EME
mCDMProxyPromise.Begin(aDecoder->RequestCDMProxy()->Then(
OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnCDMProxyReady,
&MediaDecoderStateMachine::OnCDMProxyNotReady));
#endif
}
MediaDecoderStateMachine::~MediaDecoderStateMachine()
{
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
MOZ_COUNT_DTOR(MediaDecoderStateMachine);
mReader = nullptr;
#ifdef XP_WIN
timeEndPeriod(1);
#endif
}
void
MediaDecoderStateMachine::InitializationTask(MediaDecoder* aDecoder)
{
MOZ_ASSERT(OnTaskQueue());
// Connect mirrors.
mBuffered.Connect(mReader->CanonicalBuffered());
mEstimatedDuration.Connect(aDecoder->CanonicalEstimatedDuration());
mExplicitDuration.Connect(aDecoder->CanonicalExplicitDuration());
mPlayState.Connect(aDecoder->CanonicalPlayState());
mNextPlayState.Connect(aDecoder->CanonicalNextPlayState());
mLogicallySeeking.Connect(aDecoder->CanonicalLogicallySeeking());
mVolume.Connect(aDecoder->CanonicalVolume());
mLogicalPlaybackRate.Connect(aDecoder->CanonicalPlaybackRate());
mPreservesPitch.Connect(aDecoder->CanonicalPreservesPitch());
mSameOriginMedia.Connect(aDecoder->CanonicalSameOriginMedia());
mMediaPrincipalHandle.Connect(aDecoder->CanonicalMediaPrincipalHandle());
mPlaybackBytesPerSecond.Connect(aDecoder->CanonicalPlaybackBytesPerSecond());
mPlaybackRateReliable.Connect(aDecoder->CanonicalPlaybackRateReliable());
mDecoderPosition.Connect(aDecoder->CanonicalDecoderPosition());
mMediaSeekable.Connect(aDecoder->CanonicalMediaSeekable());
// Initialize watchers.
mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
mWatchManager.Watch(mVideoCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration);
mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration);
mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::LogicallySeekingChanged);
}
media::MediaSink*
MediaDecoderStateMachine::CreateAudioSink()
{
RefPtr<MediaDecoderStateMachine> self = this;
auto audioSinkCreator = [self] () {
MOZ_ASSERT(self->OnTaskQueue());
return new DecodedAudioDataSink(
self->mAudioQueue, self->GetMediaTime(),
self->mInfo.mAudio, self->mAudioChannel);
};
return new AudioSinkWrapper(mTaskQueue, audioSinkCreator);
}
already_AddRefed<media::MediaSink>
MediaDecoderStateMachine::CreateMediaSink(bool aAudioCaptured)
{
RefPtr<media::MediaSink> audioSink = aAudioCaptured
? new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue,
mOutputStreamManager, mSameOriginMedia.Ref(),
mMediaPrincipalHandle.Ref())
: CreateAudioSink();
RefPtr<media::MediaSink> mediaSink =
new VideoSink(mTaskQueue, audioSink, mVideoQueue,
mVideoFrameContainer, mRealTime,
*mFrameStats,
sVideoQueueSendToCompositorSize);
return mediaSink.forget();
}
bool MediaDecoderStateMachine::HasFutureAudio()
{
MOZ_ASSERT(OnTaskQueue());
NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
// We've got audio ready to play if:
// 1. We've not completed playback of audio, and
// 2. we either have more than the threshold of decoded audio available, or
// we've completely decoded all audio (but not finished playing it yet
// as per 1).
return !mAudioCompleted &&
(AudioDecodedUsecs() >
mLowAudioThresholdUsecs * mPlaybackRate ||
AudioQueue().IsFinished());
}
bool MediaDecoderStateMachine::HaveNextFrameData()
{
MOZ_ASSERT(OnTaskQueue());
return (!HasAudio() || HasFutureAudio()) &&
(!HasVideo() || VideoQueue().GetSize() > 1);
}
int64_t
MediaDecoderStateMachine::GetDecodedAudioDuration()
{
MOZ_ASSERT(OnTaskQueue());
if (mMediaSink->IsStarted()) {
// mDecodedAudioEndTime might be smaller than GetClock() when there is
// overlap between 2 adjacent audio samples or when we are playing
// a chained ogg file.
return std::max<int64_t>(mDecodedAudioEndTime - GetClock(), 0);
}
// MediaSink not started. All audio samples are in the queue.
return AudioQueue().Duration();
}
void MediaDecoderStateMachine::DiscardStreamData()
{
MOZ_ASSERT(OnTaskQueue());
const auto clockTime = GetClock();
while (true) {
const MediaData* a = AudioQueue().PeekFront();
// If we discard audio samples fed to the stream immediately, we will
// keep decoding audio samples till the end and consume a lot of memory.
// Therefore we only discard those behind the stream clock to throttle
// the decoding speed.
// Note we don't discard a sample when |a->mTime == clockTime| because that
// will discard the 1st sample when clockTime is still 0.
if (a && a->mTime < clockTime) {
RefPtr<MediaData> releaseMe = AudioQueue().PopFront();
continue;
}
break;
}
}
bool MediaDecoderStateMachine::HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs)
{
MOZ_ASSERT(OnTaskQueue());
if (AudioQueue().GetSize() == 0 ||
GetDecodedAudioDuration() < aAmpleAudioUSecs) {
return false;
}
// MDSM will ensure buffering level is high enough for playback speed at 1x
// at which the DecodedStream is playing.
return true;
}
bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
{
MOZ_ASSERT(OnTaskQueue());
if (VideoQueue().GetSize() == 0) {
return false;
}
if (VideoQueue().GetSize() - 1 < GetAmpleVideoFrames() * mPlaybackRate) {
return false;
}
return true;
}
bool
MediaDecoderStateMachine::NeedToDecodeVideo()
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("NeedToDecodeVideo() isDec=%d minPrl=%d enufVid=%d",
IsVideoDecoding(), mMinimizePreroll, HaveEnoughDecodedVideo());
return IsVideoDecoding() &&
mState != DECODER_STATE_SEEKING &&
((IsDecodingFirstFrame() && VideoQueue().GetSize() == 0) ||
(!mMinimizePreroll && !HaveEnoughDecodedVideo()));
}
bool
MediaDecoderStateMachine::NeedToSkipToNextKeyframe()
{
MOZ_ASSERT(OnTaskQueue());
if (IsDecodingFirstFrame()) {
return false;
}
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
mState == DECODER_STATE_BUFFERING ||
mState == DECODER_STATE_SEEKING);
// Since GetClock() can only be called after starting MediaSink, we return
// false quickly if it is not started because we won't fall behind playback
// when not consuming media data.
if (!mMediaSink->IsStarted()) {
return false;
}
// We are in seeking or buffering states, don't skip frame.
if (!IsVideoDecoding() || mState == DECODER_STATE_BUFFERING ||
mState == DECODER_STATE_SEEKING) {
return false;
}
// Don't skip frame for video-only decoded stream because the clock time of
// the stream relies on the video frame.
if (mAudioCaptured && !HasAudio()) {
return false;
}
// We'll skip the video decode to the next keyframe if we're low on
// audio, or if we're low on video, provided we're not running low on
// data to decode. If we're running low on downloaded data to decode,
// we won't start keyframe skipping, as we'll be pausing playback to buffer
// soon anyway and we'll want to be able to display frames immediately
// after buffering finishes. We ignore the low audio calculations for
// readers that are async, as since their audio decode runs on a different
// task queue it should never run low and skipping won't help their decode.
bool isLowOnDecodedAudio = !mReader->IsAsync() &&
!mIsAudioPrerolling && IsAudioDecoding() &&
(GetDecodedAudioDuration() <
mLowAudioThresholdUsecs * mPlaybackRate);
bool isLowOnDecodedVideo = !mIsVideoPrerolling &&
((GetClock() - mDecodedVideoEndTime) * mPlaybackRate >
LOW_VIDEO_THRESHOLD_USECS);
bool lowUndecoded = HasLowUndecodedData();
if ((isLowOnDecodedAudio || isLowOnDecodedVideo) && !lowUndecoded) {
DECODER_LOG("Skipping video decode to the next keyframe lowAudio=%d lowVideo=%d lowUndecoded=%d async=%d",
isLowOnDecodedAudio, isLowOnDecodedVideo, lowUndecoded, mReader->IsAsync());
return true;
}
return false;
}
bool
MediaDecoderStateMachine::NeedToDecodeAudio()
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("NeedToDecodeAudio() isDec=%d minPrl=%d enufAud=%d",
IsAudioDecoding(), mMinimizePreroll,
HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate));
return IsAudioDecoding() &&
mState != DECODER_STATE_SEEKING &&
((IsDecodingFirstFrame() && AudioQueue().GetSize() == 0) ||
(!mMinimizePreroll &&
!HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate)));
}
bool
MediaDecoderStateMachine::IsAudioSeekComplete()
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d mAudDis=%d aqFin=%d aqSz=%d",
mCurrentSeek.Exists(), mDropAudioUntilNextDiscontinuity, AudioQueue().IsFinished(), AudioQueue().GetSize());
return
!HasAudio() ||
(mCurrentSeek.Exists() &&
!mDropAudioUntilNextDiscontinuity &&
(AudioQueue().IsFinished() || AudioQueue().GetSize() > 0));
}
bool
MediaDecoderStateMachine::IsVideoSeekComplete()
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("IsVideoSeekComplete() curTarVal=%d mVidDis=%d vqFin=%d vqSz=%d",
mCurrentSeek.Exists(), mDropVideoUntilNextDiscontinuity, VideoQueue().IsFinished(), VideoQueue().GetSize());
return
!HasVideo() ||
(mCurrentSeek.Exists() &&
!mDropVideoUntilNextDiscontinuity &&
(VideoQueue().IsFinished() || VideoQueue().GetSize() > 0));
}
void
MediaDecoderStateMachine::OnAudioDecoded(MediaData* aAudioSample)
{
MOZ_ASSERT(OnTaskQueue());
RefPtr<MediaData> audio(aAudioSample);
MOZ_ASSERT(audio);
mAudioDataRequest.Complete();
aAudioSample->AdjustForStartTime(StartTime());
// audio->GetEndTime() is not always mono-increasing in chained ogg.
mDecodedAudioEndTime = std::max(audio->GetEndTime(), mDecodedAudioEndTime);
SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d",
(audio ? audio->mTime : -1),
(audio ? audio->GetEndTime() : -1),
(audio ? audio->mDiscontinuity : 0));
switch (mState) {
case DECODER_STATE_BUFFERING: {
// If we're buffering, this may be the sample we need to stop buffering.
// Save it and schedule the state machine.
Push(audio, MediaData::AUDIO_DATA);
ScheduleStateMachine();
return;
}
case DECODER_STATE_DECODING: {
Push(audio, MediaData::AUDIO_DATA);
if (MaybeFinishDecodeFirstFrame()) {
return;
}
if (mIsAudioPrerolling && DonePrerollingAudio()) {
StopPrerollingAudio();
}
return;
}
case DECODER_STATE_SEEKING: {
if (!mCurrentSeek.Exists()) {
// We've received a sample from a previous decode. Discard it.
return;
}
if (audio->mDiscontinuity) {
mDropAudioUntilNextDiscontinuity = false;
}
if (!mDropAudioUntilNextDiscontinuity) {
// We must be after the discontinuity; we're receiving samples
// at or after the seek target.
if (mCurrentSeek.mTarget.IsFast() &&
mCurrentSeek.mTarget.GetTime().ToMicroseconds() > mCurrentTimeBeforeSeek &&
audio->mTime < mCurrentTimeBeforeSeek) {
// We are doing a fastSeek, but we ended up *before* the previous
// playback position. This is surprising UX, so switch to an accurate
// seek and decode to the seek target. This is not conformant to the
// spec, fastSeek should always be fast, but until we get the time to
// change all Readers to seek to the keyframe after the currentTime
// in this case, we'll just decode forward. Bug 1026330.
mCurrentSeek.mTarget.SetType(SeekTarget::Accurate);
}
if (mCurrentSeek.mTarget.IsFast()) {
// Non-precise seek; we can stop the seek at the first sample.
Push(audio, MediaData::AUDIO_DATA);
} else {
// We're doing an accurate seek. We must discard
// MediaData up to the one containing exact seek target.
if (NS_FAILED(DropAudioUpToSeekTarget(audio))) {
DecodeError();
return;
}
}
}
CheckIfSeekComplete();
return;
}
default: {
// Ignore other cases.
return;
}
}
}
void
MediaDecoderStateMachine::Push(MediaData* aSample, MediaData::Type aSampleType)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(aSample);
if (aSample->mType == MediaData::AUDIO_DATA) {
// TODO: Send aSample to MSG and recalculate readystate before pushing,
// otherwise AdvanceFrame may pop the sample before we have a chance
// to reach playing.
AudioQueue().Push(aSample);
} else if (aSample->mType == MediaData::VIDEO_DATA) {
// TODO: Send aSample to MSG and recalculate readystate before pushing,
// otherwise AdvanceFrame may pop the sample before we have a chance
// to reach playing.
aSample->As<VideoData>()->mFrameID = ++mCurrentFrameID;
VideoQueue().Push(aSample);
} else {
// TODO: Handle MediaRawData, determine which queue should be pushed.
}
UpdateNextFrameStatus();
DispatchDecodeTasksIfNeeded();
}
void
MediaDecoderStateMachine::PushFront(MediaData* aSample, MediaData::Type aSampleType)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(aSample);
if (aSample->mType == MediaData::AUDIO_DATA) {
AudioQueue().PushFront(aSample);
} else if (aSample->mType == MediaData::VIDEO_DATA) {
aSample->As<VideoData>()->mFrameID = ++mCurrentFrameID;
VideoQueue().PushFront(aSample);
} else {
// TODO: Handle MediaRawData, determine which queue should be pushed.
}
UpdateNextFrameStatus();
}
void
MediaDecoderStateMachine::CheckIsAudible(const MediaData* aSample)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(aSample->mType == MediaData::AUDIO_DATA);
const AudioData* data = aSample->As<AudioData>();
bool isAudible = data->IsAudible();
if (isAudible && !mIsAudioDataAudible) {
mIsAudioDataAudible = true;
mSilentDataDuration = 0;
} else if (isAudible && mIsAudioDataAudible) {
mSilentDataDuration += data->mDuration;
if (mSilentDataDuration > SILENT_DATA_THRESHOLD_USECS) {
mIsAudioDataAudible = false;
mSilentDataDuration = 0;
}
}
}
void
MediaDecoderStateMachine::OnAudioPopped(const RefPtr<MediaData>& aSample)
{
MOZ_ASSERT(OnTaskQueue());
mPlaybackOffset = std::max(mPlaybackOffset.Ref(), aSample->mOffset);
UpdateNextFrameStatus();
DispatchAudioDecodeTaskIfNeeded();
MaybeStartBuffering();
CheckIsAudible(aSample);
}
void
MediaDecoderStateMachine::OnVideoPopped(const RefPtr<MediaData>& aSample)
{
MOZ_ASSERT(OnTaskQueue());
mPlaybackOffset = std::max(mPlaybackOffset.Ref(), aSample->mOffset);
UpdateNextFrameStatus();
DispatchVideoDecodeTaskIfNeeded();
MaybeStartBuffering();
}
void
MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
MediaDecoderReader::NotDecodedReason aReason)
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("OnNotDecoded (aType=%u, aReason=%u)", aType, aReason);
bool isAudio = aType == MediaData::AUDIO_DATA;
MOZ_ASSERT_IF(!isAudio, aType == MediaData::VIDEO_DATA);
if (isAudio) {
mAudioDataRequest.Complete();
} else {
mVideoDataRequest.Complete();
}
if (IsShutdown()) {
// Already shutdown;
return;
}
// If this is a decode error, delegate to the generic error path.
if (aReason == MediaDecoderReader::DECODE_ERROR) {
DecodeError();
return;
}
// If the decoder is waiting for data, we tell it to call us back when the
// data arrives.
if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
"Readers that send WAITING_FOR_DATA need to implement WaitForData");
RefPtr<MediaDecoderStateMachine> self = this;
WaitRequestRef(aType).Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::WaitForData, aType)
->Then(OwnerThread(), __func__,
[self] (MediaData::Type aType) -> void {
self->WaitRequestRef(aType).Complete();
self->DispatchDecodeTasksIfNeeded();
},
[self] (WaitForDataRejectValue aRejection) -> void {
self->WaitRequestRef(aRejection.mType).Complete();
}));
// We are out of data to decode and will enter buffering mode soon.
// We want to play the frames we have already decoded, so we stop pre-rolling
// and ensure that loadeddata is fired as required.
if (isAudio) {
StopPrerollingAudio();
} else {
StopPrerollingVideo();
}
if (mState == DECODER_STATE_BUFFERING || mState == DECODER_STATE_DECODING) {
MaybeFinishDecodeFirstFrame();
}
return;
}
if (aReason == MediaDecoderReader::CANCELED) {
DispatchDecodeTasksIfNeeded();
return;
}
// This is an EOS. Finish off the queue, and then handle things based on our
// state.
MOZ_ASSERT(aReason == MediaDecoderReader::END_OF_STREAM);
if (!isAudio && mState == DECODER_STATE_SEEKING &&
mCurrentSeek.Exists() && mFirstVideoFrameAfterSeek) {
// Null sample. Hit end of stream. If we have decoded a frame,
// insert it into the queue so that we have something to display.
// We make sure to do this before invoking VideoQueue().Finish()
// below.
Push(mFirstVideoFrameAfterSeek, MediaData::VIDEO_DATA);
mFirstVideoFrameAfterSeek = nullptr;
}
if (isAudio) {
AudioQueue().Finish();
StopPrerollingAudio();
} else {
VideoQueue().Finish();
StopPrerollingVideo();
}
switch (mState) {
case DECODER_STATE_BUFFERING:
case DECODER_STATE_DECODING: {
if (MaybeFinishDecodeFirstFrame()) {
return;
}
CheckIfDecodeComplete();
return;
}
case DECODER_STATE_SEEKING: {
if (!mCurrentSeek.Exists()) {
// We've received a sample from a previous decode. Discard it.
return;
}
if (isAudio) {
mDropAudioUntilNextDiscontinuity = false;
} else {
mDropVideoUntilNextDiscontinuity = false;
}
CheckIfSeekComplete();
return;
}
default: {
return;
}
}
}
bool
MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
{
MOZ_ASSERT(OnTaskQueue());
if (!IsDecodingFirstFrame() ||
(IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
(IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
return false;
}
FinishDecodeFirstFrame();
if (!mQueuedSeek.Exists()) {
return false;
}
// We can now complete the pending seek.
mPendingSeek.Steal(mQueuedSeek);
SetState(DECODER_STATE_SEEKING);
ScheduleStateMachine();
return true;
}
void
MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample)
{
MOZ_ASSERT(OnTaskQueue());
RefPtr<MediaData> video(aVideoSample);
MOZ_ASSERT(video);
mVideoDataRequest.Complete();
aVideoSample->AdjustForStartTime(StartTime());
// Handle abnormal or negative timestamps.
mDecodedVideoEndTime = std::max(mDecodedVideoEndTime, video->GetEndTime());
SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
(video ? video->mTime : -1),
(video ? video->GetEndTime() : -1),
(video ? video->mDiscontinuity : 0));
switch (mState) {
case DECODER_STATE_BUFFERING: {
// If we're buffering, this may be the sample we need to stop buffering.
// Save it and schedule the state machine.
Push(video, MediaData::VIDEO_DATA);
ScheduleStateMachine();
return;
}
case DECODER_STATE_DECODING: {
Push(video, MediaData::VIDEO_DATA);
if (MaybeFinishDecodeFirstFrame()) {
return;
}
if (mIsVideoPrerolling && DonePrerollingVideo()) {
StopPrerollingVideo();
}
// For non async readers, if the requested video sample was slow to
// arrive, increase the amount of audio we buffer to ensure that we
// don't run out of audio. This is unnecessary for async readers,
// since they decode audio and video on different threads so they
// are unlikely to run out of decoded audio.
if (mReader->IsAsync()) {
return;
}
TimeDuration decodeTime = TimeStamp::Now() - mVideoDecodeStartTime;
if (!IsDecodingFirstFrame() &&
THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
!HasLowUndecodedData())
{
mLowAudioThresholdUsecs =
std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), mAmpleAudioThresholdUsecs);
mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
mAmpleAudioThresholdUsecs);
DECODER_LOG("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs);
}
return;
}
case DECODER_STATE_SEEKING: {
if (!mCurrentSeek.Exists()) {
// We've received a sample from a previous decode. Discard it.
return;
}
if (mDropVideoUntilNextDiscontinuity) {
if (video->mDiscontinuity) {
mDropVideoUntilNextDiscontinuity = false;
}
}
if (!mDropVideoUntilNextDiscontinuity) {
// We must be after the discontinuity; we're receiving samples
// at or after the seek target.
if (mCurrentSeek.mTarget.IsFast() &&
mCurrentSeek.mTarget.GetTime().ToMicroseconds() > mCurrentTimeBeforeSeek &&
video->mTime < mCurrentTimeBeforeSeek) {
// We are doing a fastSeek, but we ended up *before* the previous
// playback position. This is surprising UX, so switch to an accurate
// seek and decode to the seek target. This is not conformant to the
// spec, fastSeek should always be fast, but until we get the time to
// change all Readers to seek to the keyframe after the currentTime
// in this case, we'll just decode forward. Bug 1026330.
mCurrentSeek.mTarget.SetType(SeekTarget::Accurate);
}
if (mCurrentSeek.mTarget.IsFast() ||
mPendingSeek.Exists()) {
// Non-precise seek; or a pending seek exists ; we can stop the seek
// at the first sample.
Push(video, MediaData::VIDEO_DATA);
} else {
// We're doing an accurate seek. We still need to discard
// MediaData up to the one containing exact seek target.
if (NS_FAILED(DropVideoUpToSeekTarget(video))) {
DecodeError();
return;
}
}
}
CheckIfSeekComplete();
return;
}
default: {
// Ignore other cases.
return;
}
}
}
void
MediaDecoderStateMachine::CheckIfSeekComplete()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mState == DECODER_STATE_SEEKING);
const bool videoSeekComplete = IsVideoSeekComplete();
if (HasVideo() && !videoSeekComplete) {
// We haven't reached the target. Ensure we have requested another sample.
if (NS_FAILED(EnsureVideoDecodeTaskQueued())) {
DECODER_WARN("Failed to request video during seek");
DecodeError();
}
}
const bool audioSeekComplete = IsAudioSeekComplete();
if (HasAudio() && !audioSeekComplete) {
// We haven't reached the target. Ensure we have requested another sample.
if (NS_FAILED(EnsureAudioDecodeTaskQueued())) {
DECODER_WARN("Failed to request audio during seek");
DecodeError();
}
}
SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
audioSeekComplete, videoSeekComplete);
if (audioSeekComplete && videoSeekComplete) {
NS_ASSERTION(AudioQueue().GetSize() <= 1, "Should decode at most one sample");
NS_ASSERTION(VideoQueue().GetSize() <= 1, "Should decode at most one sample");
SeekCompleted();
}
}
bool
MediaDecoderStateMachine::IsAudioDecoding()
{
MOZ_ASSERT(OnTaskQueue());
return HasAudio() && !AudioQueue().IsFinished();
}
bool
MediaDecoderStateMachine::IsVideoDecoding()
{
MOZ_ASSERT(OnTaskQueue());
return HasVideo() && !VideoQueue().IsFinished();
}
void
MediaDecoderStateMachine::CheckIfDecodeComplete()
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown() ||
mState == DECODER_STATE_SEEKING ||
mState == DECODER_STATE_COMPLETED) {
// Don't change our state if we've already been shutdown, or we're seeking,
// since we don't want to abort the shutdown or seek processes.
return;
}
if (!IsVideoDecoding() && !IsAudioDecoding()) {
// We've finished decoding all active streams,
// so move to COMPLETED state.
SetState(DECODER_STATE_COMPLETED);
DispatchDecodeTasksIfNeeded();
ScheduleStateMachine();
}
DECODER_LOG("CheckIfDecodeComplete %scompleted",
((mState == DECODER_STATE_COMPLETED) ? "" : "NOT "));
}
bool MediaDecoderStateMachine::IsPlaying() const
{
MOZ_ASSERT(OnTaskQueue());
return mMediaSink->IsPlaying();
}
nsresult MediaDecoderStateMachine::Init()
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mReader->Init();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
this, &MediaDecoderStateMachine::ReadMetadata);
OwnerThread()->Dispatch(r.forget());
return NS_OK;
}
void MediaDecoderStateMachine::StopPlayback()
{
MOZ_ASSERT(OnTaskQueue());
DECODER_LOG("StopPlayback()");
mOnPlaybackEvent.Notify(MediaEventType::PlaybackStopped);
if (IsPlaying()) {
mMediaSink->SetPlaying(false);
MOZ_ASSERT(!IsPlaying());
}
DispatchDecodeTasksIfNeeded();
}
void MediaDecoderStateMachine::MaybeStartPlayback()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
mState == DECODER_STATE_COMPLETED);
if (IsPlaying()) {
// Logging this case is really spammy - don't do it.
return;
}
bool playStatePermits = mPlayState == MediaDecoder::PLAY_STATE_PLAYING;
if (!playStatePermits || mIsAudioPrerolling ||
mIsVideoPrerolling || mAudioOffloading) {
DECODER_LOG("Not starting playback [playStatePermits: %d, "
"mIsAudioPrerolling: %d, mIsVideoPrerolling: %d, "
"mAudioOffloading: %d]",
(int)playStatePermits, (int)mIsAudioPrerolling,
(int)mIsVideoPrerolling, (int)mAudioOffloading);
return;
}
DECODER_LOG("MaybeStartPlayback() starting playback");
mOnPlaybackEvent.Notify(MediaEventType::PlaybackStarted);
StartMediaSink();
if (!IsPlaying()) {
mMediaSink->SetPlaying(true);
MOZ_ASSERT(IsPlaying());
}
DispatchDecodeTasksIfNeeded();
}
void
MediaDecoderStateMachine::MaybeStartBuffering()
{
MOZ_ASSERT(OnTaskQueue());
if (mState == DECODER_STATE_DECODING &&
mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
mResource->IsExpectingMoreData()) {
bool shouldBuffer;
if (mReader->UseBufferingHeuristics()) {
shouldBuffer = HasLowDecodedData(EXHAUSTED_DATA_MARGIN_USECS) &&
(JustExitedQuickBuffering() || HasLowUndecodedData());
} else {
MOZ_ASSERT(mReader->IsWaitForDataSupported());
shouldBuffer = (OutOfDecodedAudio() && mAudioWaitRequest.Exists()) ||
(OutOfDecodedVideo() && mVideoWaitRequest.Exists());
}
if (shouldBuffer) {
StartBuffering();
// Don't go straight back to the state machine loop since that might
// cause us to start decoding again and we could flip-flop between
// decoding and quick-buffering.
ScheduleStateMachineIn(USECS_PER_S);
}
}
}
void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime)
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("UpdatePlaybackPositionInternal(%lld)", aTime);
mCurrentPosition = aTime;
NS_ASSERTION(mCurrentPosition >= 0, "CurrentTime should be positive!");
mObservedDuration = std::max(mObservedDuration.Ref(),
TimeUnit::FromMicroseconds(mCurrentPosition.Ref()));
}
void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
{
MOZ_ASSERT(OnTaskQueue());
UpdatePlaybackPositionInternal(aTime);
bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
mMetadataManager.DispatchMetadataIfNeeded(TimeUnit::FromMicroseconds(aTime));
if (fragmentEnded) {
StopPlayback();
}
}
static const char* const gMachineStateStr[] = {
"DECODING_METADATA",
"WAIT_FOR_CDM",
"DORMANT",
"DECODING",
"SEEKING",
"BUFFERING",
"COMPLETED",
"SHUTDOWN",
"ERROR"
};
void MediaDecoderStateMachine::SetState(State aState)
{
MOZ_ASSERT(OnTaskQueue());
if (mState == aState) {
return;
}
DECODER_LOG("Change machine state from %s to %s",
gMachineStateStr[mState], gMachineStateStr[aState]);
mState = aState;
mIsShutdown = mState == DECODER_STATE_ERROR || mState == DECODER_STATE_SHUTDOWN;
// Clear state-scoped state.
mSentPlaybackEndedEvent = false;
}
void MediaDecoderStateMachine::VolumeChanged()
{
MOZ_ASSERT(OnTaskQueue());
mMediaSink->SetVolume(mVolume);
}
void MediaDecoderStateMachine::RecomputeDuration()
{
MOZ_ASSERT(OnTaskQueue());
TimeUnit duration;
if (mExplicitDuration.Ref().isSome()) {
double d = mExplicitDuration.Ref().ref();
if (IsNaN(d)) {
// We have an explicit duration (which means that we shouldn't look at
// any other duration sources), but the duration isn't ready yet.
return;
}
// We don't fire duration changed for this case because it should have
// already been fired on the main thread when the explicit duration was set.
duration = TimeUnit::FromSeconds(d);
} else if (mEstimatedDuration.Ref().isSome()) {
duration = mEstimatedDuration.Ref().ref();
} else if (mInfo.mMetadataDuration.isSome()) {
duration = mInfo.mMetadataDuration.ref();
} else {
return;
}
if (duration < mObservedDuration.Ref()) {
duration = mObservedDuration;
}
MOZ_ASSERT(duration.ToMicroseconds() >= 0);
mDuration = Some(duration);
}
void
MediaDecoderStateMachine::DispatchSetDormant(bool aDormant)
{
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<bool>(
this, &MediaDecoderStateMachine::SetDormant, aDormant);
OwnerThread()->Dispatch(r.forget());
}
void
MediaDecoderStateMachine::SetDormant(bool aDormant)
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
return;
}
if (!mReader) {
return;
}
if (mMetadataRequest.Exists()) {
if (mPendingDormant && mPendingDormant.ref() != aDormant && !aDormant) {
// We already have a dormant request pending; the new request would have
// resumed from dormant, we can just cancel any pending dormant requests.
mPendingDormant.reset();
} else {
mPendingDormant = Some(aDormant);
}
return;
}
mPendingDormant.reset();
DECODER_LOG("SetDormant=%d", aDormant);
if (aDormant) {
if (mState == DECODER_STATE_SEEKING) {
if (mQueuedSeek.Exists()) {
// Keep latest seek target
} else if (mPendingSeek.Exists()) {
mQueuedSeek.Steal(mPendingSeek);
} else if (mCurrentSeek.Exists()) {
mQueuedSeek.Steal(mCurrentSeek);
} else {
mQueuedSeek.mTarget = SeekTarget(mCurrentPosition,
SeekTarget::Accurate,
MediaDecoderEventVisibility::Suppressed);
// Nobody is listening to this promise. Do we need to pass it
// back to MediaDecoder when we come out of dormant?
RefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
}
} else {
mQueuedSeek.mTarget = SeekTarget(mCurrentPosition,
SeekTarget::Accurate,
MediaDecoderEventVisibility::Suppressed);
// Nobody is listening to this promise. Do we need to pass it
// back to MediaDecoder when we come out of dormant?
RefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
}
mPendingSeek.RejectIfExists(__func__);
mCurrentSeek.RejectIfExists(__func__);
SetState(DECODER_STATE_DORMANT);
if (IsPlaying()) {
StopPlayback();
}
Reset();
// Note that we do not wait for the decode task queue to go idle before
// queuing the ReleaseMediaResources task - instead, we disconnect promises,
// reset state, and put a ResetDecode in the decode task queue. Any tasks
// that run after ResetDecode are supposed to run with a clean slate. We rely
// on that in other places (i.e. seeking), so it seems reasonable to rely on
// it here as well.
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources);
DecodeTaskQueue()->Dispatch(r.forget());
} else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
mDecodingFirstFrame = true;
SetState(DECODER_STATE_DECODING_METADATA);
ReadMetadata();
}
}
RefPtr<ShutdownPromise>
MediaDecoderStateMachine::Shutdown()
{
MOZ_ASSERT(OnTaskQueue());
// Once we've entered the shutdown state here there's no going back.
// Change state before issuing shutdown request to threads so those
// threads can start exiting cleanly during the Shutdown call.
ScheduleStateMachine();
SetState(DECODER_STATE_SHUTDOWN);
mBufferedUpdateRequest.DisconnectIfExists();
mQueuedSeek.RejectIfExists(__func__);
mPendingSeek.RejectIfExists(__func__);
mCurrentSeek.RejectIfExists(__func__);
#ifdef MOZ_EME
mCDMProxyPromise.DisconnectIfExists();
#endif
if (IsPlaying()) {
StopPlayback();
}
Reset();
mMediaSink->Shutdown();
// Shut down our start time rendezvous.
if (mStartTimeRendezvous) {
mStartTimeRendezvous->Destroy();
}
DECODER_LOG("Shutdown started");
// Put a task in the decode queue to shutdown the reader.
// the queue to spin down.
return InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::Shutdown)
->Then(OwnerThread(), __func__, this,
&MediaDecoderStateMachine::FinishShutdown,
&MediaDecoderStateMachine::FinishShutdown)
->CompletionPromise();
}
void MediaDecoderStateMachine::StartDecoding()
{
MOZ_ASSERT(OnTaskQueue());
if (mState == DECODER_STATE_DECODING && !mDecodingFirstFrame) {
return;
}
SetState(DECODER_STATE_DECODING);
if (mDecodingFirstFrame &&
(IsRealTime() || mSentFirstFrameLoadedEvent)) {
if (IsRealTime()) {
FinishDecodeFirstFrame();
} else {
// We're resuming from dormant state, so we don't need to request
// the first samples in order to determine the media start time,
// we have the start time from last time we loaded.
// FinishDecodeFirstFrame will be launched upon completion of the seek when
// we have data ready to play.
MOZ_ASSERT(mQueuedSeek.Exists() && mSentFirstFrameLoadedEvent,
"Return from dormant must have queued seek");
}
if (mQueuedSeek.Exists()) {
mPendingSeek.Steal(mQueuedSeek);
SetState(DECODER_STATE_SEEKING);
ScheduleStateMachine();
return;
}
}
mDecodeStartTime = TimeStamp::Now();
CheckIfDecodeComplete();
if (mState == DECODER_STATE_COMPLETED) {
return;
}
// Reset other state to pristine values before starting decode.
mIsAudioPrerolling = !DonePrerollingAudio();
mIsVideoPrerolling = !DonePrerollingVideo();
// Ensure that we've got tasks enqueued to decode data if we need to.
DispatchDecodeTasksIfNeeded();
ScheduleStateMachine();
}
void MediaDecoderStateMachine::PlayStateChanged()
{
MOZ_ASSERT(OnTaskQueue());
// This method used to be a Play() method invoked by MediaDecoder when the
// play state became PLAY_STATE_PLAYING. As such, it doesn't have any work to
// do for other state changes. That could change.
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
return;
}
// Once we start playing, we don't want to minimize our prerolling, as we
// assume the user is likely to want to keep playing in future. This needs to
// happen before we invoke StartDecoding().
if (mMinimizePreroll) {
mMinimizePreroll = false;
DispatchDecodeTasksIfNeeded();
}
// Some state transitions still happen synchronously on the main thread. So
// if the main thread invokes Play() and then Seek(), the seek will initiate
// synchronously on the main thread, and the asynchronous PlayInternal task
// will arrive when it's no longer valid. The proper thing to do is to move
// all state transitions to the state machine task queue, but for now we just
// make sure that none of the possible main-thread state transitions (Seek(),
// SetDormant(), and Shutdown()) have not occurred.
if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_COMPLETED)
{
DECODER_LOG("Unexpected state - Bailing out of PlayInternal()");
return;
}
// When asked to play, switch to decoding state only if
// we are currently buffering. In other cases, we'll start playing anyway
// when the state machine notices the decoder's state change to PLAYING.
if (mState == DECODER_STATE_BUFFERING) {
StartDecoding();
}
ScheduleStateMachine();
}
void MediaDecoderStateMachine::LogicallySeekingChanged()
{
MOZ_ASSERT(OnTaskQueue());
ScheduleStateMachine();
}
void MediaDecoderStateMachine::BufferedRangeUpdated()
{
MOZ_ASSERT(OnTaskQueue());
// While playing an unseekable stream of unknown duration, mObservedDuration
// is updated (in AdvanceFrame()) as we play. But if data is being downloaded
// faster than played, mObserved won't reflect the end of playable data
// since we haven't played the frame at the end of buffered data. So update
// mObservedDuration here as new data is downloaded to prevent such a lag.
if (!mBuffered.Ref().IsInvalid()) {
bool exists;
media::TimeUnit end{mBuffered.Ref().GetEnd(&exists)};
if (exists) {
mObservedDuration = std::max(mObservedDuration.Ref(), end);
}
}
}
void
MediaDecoderStateMachine::ReadMetadata()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(!IsShutdown());
MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
MOZ_ASSERT(!mMetadataRequest.Exists());
DECODER_LOG("Dispatching AsyncReadMetadata");
// Set mode to METADATA since we are about to read metadata.
mResource->SetReadMode(MediaCacheStream::MODE_METADATA);
mMetadataRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::AsyncReadMetadata)
->Then(OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnMetadataRead,
&MediaDecoderStateMachine::OnMetadataNotRead));
}
RefPtr<MediaDecoder::SeekPromise>
MediaDecoderStateMachine::Seek(SeekTarget aTarget)
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
}
// We need to be able to seek both at a transport level and at a media level
// to seek.
if (!mMediaSeekable) {
DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
}
MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
"We should have got duration already");
if (mState < DECODER_STATE_DECODING ||
(IsDecodingFirstFrame() && !mReader->ForceZeroStartTime())) {
DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
mQueuedSeek.RejectIfExists(__func__);
mQueuedSeek.mTarget = aTarget;
return mQueuedSeek.mPromise.Ensure(__func__);
}
mQueuedSeek.RejectIfExists(__func__);
mPendingSeek.RejectIfExists(__func__);
mPendingSeek.mTarget = aTarget;
DECODER_LOG("Changed state to SEEKING (to %lld)", mPendingSeek.mTarget.GetTime().ToMicroseconds());
SetState(DECODER_STATE_SEEKING);
ScheduleStateMachine();
return mPendingSeek.mPromise.Ensure(__func__);
}
RefPtr<MediaDecoder::SeekPromise>
MediaDecoderStateMachine::InvokeSeek(SeekTarget aTarget)
{
return InvokeAsync(OwnerThread(), this, __func__,
&MediaDecoderStateMachine::Seek, aTarget);
}
void MediaDecoderStateMachine::StopMediaSink()
{
MOZ_ASSERT(OnTaskQueue());
if (mMediaSink->IsStarted()) {
DECODER_LOG("Stop MediaSink");
mMediaSink->Stop();
mMediaSinkAudioPromise.DisconnectIfExists();
mMediaSinkVideoPromise.DisconnectIfExists();
}
}
void
MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
{
MOZ_ASSERT(OnTaskQueue());
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_SEEKING) {
return;
}
// NeedToDecodeAudio() can go from false to true while we hold the
// monitor, but it can't go from true to false. This can happen because
// NeedToDecodeAudio() takes into account the amount of decoded audio
// that's been written to the AudioStream but not played yet. So if we
// were calling NeedToDecodeAudio() twice and we thread-context switch
// between the calls, audio can play, which can affect the return value
// of NeedToDecodeAudio() giving inconsistent results. So we cache the
// value returned by NeedToDecodeAudio(), and make decisions
// based on the cached value. If NeedToDecodeAudio() has
// returned false, and then subsequently returns true and we're not
// playing, it will probably be OK since we don't need to consume data
// anyway.
const bool needToDecodeAudio = NeedToDecodeAudio();
const bool needToDecodeVideo = NeedToDecodeVideo();
// If we're in completed state, we should not need to decode anything else.
MOZ_ASSERT(mState != DECODER_STATE_COMPLETED ||
(!needToDecodeAudio && !needToDecodeVideo));
bool needIdle = !IsLogicallyPlaying() &&
mState != DECODER_STATE_SEEKING &&
!needToDecodeAudio &&
!needToDecodeVideo &&
!IsPlaying();
SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d audioStatus=%s needVideo=%d videoStatus=%s needIdle=%d",
needToDecodeAudio, AudioRequestStatus(),
needToDecodeVideo, VideoRequestStatus(),
needIdle);
if (needToDecodeAudio) {
EnsureAudioDecodeTaskQueued();
}
if (needToDecodeVideo) {
EnsureVideoDecodeTaskQueued();
}
if (needIdle) {
DECODER_LOG("Dispatching SetIdle() audioQueue=%lld videoQueue=%lld",
GetDecodedAudioDuration(),
VideoQueue().Duration());
nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle);
DecodeTaskQueue()->Dispatch(task.forget());
}
}
void
MediaDecoderStateMachine::InitiateSeek()
{
MOZ_ASSERT(OnTaskQueue());
mCurrentSeek.RejectIfExists(__func__);
mCurrentSeek.Steal(mPendingSeek);
// Bound the seek time to be inside the media range.
int64_t end = Duration().ToMicroseconds();
NS_ASSERTION(end != -1, "Should know end time by now");
int64_t seekTime = mCurrentSeek.mTarget.GetTime().ToMicroseconds();
seekTime = std::min(seekTime, end);
seekTime = std::max(int64_t(0), seekTime);
NS_ASSERTION(seekTime >= 0 && seekTime <= end,
"Can only seek in range [0,duration]");
mCurrentSeek.mTarget.SetTime(media::TimeUnit::FromMicroseconds(seekTime));
mDropAudioUntilNextDiscontinuity = HasAudio();
mDropVideoUntilNextDiscontinuity = HasVideo();
mCurrentTimeBeforeSeek = GetMediaTime();
// Stop playback now to ensure that while we're outside the monitor
// dispatching SeekingStarted, playback doesn't advance and mess with
// mCurrentPosition that we've setting to seekTime here.
StopPlayback();
UpdatePlaybackPositionInternal(mCurrentSeek.mTarget.GetTime().ToMicroseconds());
mOnSeekingStart.Notify(mCurrentSeek.mTarget.mEventVisibility);
// Reset our state machine and decoding pipeline before seeking.
Reset();
// Do the seek.
RefPtr<MediaDecoderStateMachine> self = this;
SeekTarget seekTarget = mCurrentSeek.mTarget;
seekTarget.SetTime(seekTarget.GetTime() + media::TimeUnit::FromMicroseconds(StartTime()));
mSeekRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::Seek, seekTarget,
Duration().ToMicroseconds())
->Then(OwnerThread(), __func__,
[self] (media::TimeUnit) -> void {
self->mSeekRequest.Complete();
// We must decode the first samples of active streams, so we can determine
// the new stream time. So dispatch tasks to do that.
self->EnsureAudioDecodeTaskQueued();
self->EnsureVideoDecodeTaskQueued();
}, [self] (nsresult aResult) -> void {
self->mSeekRequest.Complete();
MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest");
self->DecodeError();
}));
}
nsresult
MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
return NS_ERROR_FAILURE;
}
if (NeedToDecodeAudio()) {
return EnsureAudioDecodeTaskQueued();
}
return NS_OK;
}
nsresult
MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d status=%s",
IsAudioDecoding(), AudioRequestStatus());
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_SEEKING) {
return NS_OK;
}
if (!IsAudioDecoding() || mAudioDataRequest.Exists() ||
mAudioWaitRequest.Exists() || mSeekRequest.Exists()) {
return NS_OK;
}
RequestAudioData();
return NS_OK;
}
void
MediaDecoderStateMachine::RequestAudioData()
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o",
AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames());
if (mSentFirstFrameLoadedEvent) {
mAudioDataRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(),
__func__, &MediaDecoderReader::RequestAudioData)
->Then(OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnAudioDecoded,
&MediaDecoderStateMachine::OnAudioNotDecoded));
} else {
mAudioDataRequest.Begin(
InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::RequestAudioData)
->Then(OwnerThread(), __func__, mStartTimeRendezvous.get(),
&StartTimeRendezvous::ProcessFirstSample<AudioDataPromise, MediaData::AUDIO_DATA>,
&StartTimeRendezvous::FirstSampleRejected<MediaData::AUDIO_DATA>)
->CompletionPromise()
->Then(OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnAudioDecoded,
&MediaDecoderStateMachine::OnAudioNotDecoded)
);
}
}
nsresult
MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
return NS_ERROR_FAILURE;
}
if (NeedToDecodeVideo()) {
return EnsureVideoDecodeTaskQueued();
}
return NS_OK;
}
nsresult
MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
{
MOZ_ASSERT(OnTaskQueue());
SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s",
IsVideoDecoding(), VideoRequestStatus());
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_SEEKING) {
return NS_OK;
}
if (!IsVideoDecoding() || mVideoDataRequest.Exists() ||
mVideoWaitRequest.Exists() || mSeekRequest.Exists()) {
return NS_OK;
}
RequestVideoData();
return NS_OK;
}
void
MediaDecoderStateMachine::RequestVideoData()
{
MOZ_ASSERT(OnTaskQueue());
// Time the video decode, so that if it's slow, we can increase our low
// audio threshold to reduce the chance of an audio underrun while we're
// waiting for a video decode to complete.
mVideoDecodeStartTime = TimeStamp::Now();
bool skipToNextKeyFrame = mSentFirstFrameLoadedEvent &&
NeedToSkipToNextKeyframe();
int64_t currentTime =
mState == DECODER_STATE_SEEKING || !mSentFirstFrameLoadedEvent
? 0 : GetMediaTime() + StartTime();
SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld",
VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
currentTime);
if (mSentFirstFrameLoadedEvent) {
mVideoDataRequest.Begin(
InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::RequestVideoData,
skipToNextKeyFrame, currentTime)
->Then(OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnVideoDecoded,
&MediaDecoderStateMachine::OnVideoNotDecoded));
} else {
mVideoDataRequest.Begin(
InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::RequestVideoData,
skipToNextKeyFrame, currentTime)
->Then(OwnerThread(), __func__, mStartTimeRendezvous.get(),
&StartTimeRendezvous::ProcessFirstSample<VideoDataPromise, MediaData::VIDEO_DATA>,
&StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>)
->CompletionPromise()
->Then(OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnVideoDecoded,
&MediaDecoderStateMachine::OnVideoNotDecoded));
}
}
void
MediaDecoderStateMachine::StartMediaSink()
{
MOZ_ASSERT(OnTaskQueue());
if (!mMediaSink->IsStarted()) {
mAudioCompleted = false;
mMediaSink->Start(GetMediaTime(), mInfo);
auto videoPromise = mMediaSink->OnEnded(TrackInfo::kVideoTrack);
auto audioPromise = mMediaSink->OnEnded(TrackInfo::kAudioTrack);
if (audioPromise) {
mMediaSinkAudioPromise.Begin(audioPromise->Then(
OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnMediaSinkAudioComplete,
&MediaDecoderStateMachine::OnMediaSinkAudioError));
}
if (videoPromise) {
mMediaSinkVideoPromise.Begin(videoPromise->Then(
OwnerThread(), __func__, this,
&MediaDecoderStateMachine::OnMediaSinkVideoComplete,
&MediaDecoderStateMachine::OnMediaSinkVideoError));
}
}
}
int64_t MediaDecoderStateMachine::AudioDecodedUsecs()
{
MOZ_ASSERT(OnTaskQueue());
NS_ASSERTION(HasAudio(),
"Should only call AudioDecodedUsecs() when we have audio");
// The amount of audio we have decoded is the amount of audio data we've
// already decoded and pushed to the hardware, plus the amount of audio
// data waiting to be pushed to the hardware.
int64_t pushed = mMediaSink->IsStarted() ? (AudioEndTime() - GetMediaTime()) : 0;
return pushed + AudioQueue().Duration();
}
bool MediaDecoderStateMachine::HasLowDecodedData(int64_t aAudioUsecs)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mReader->UseBufferingHeuristics());
// We consider ourselves low on decoded data if we're low on audio,
// provided we've not decoded to the end of the audio stream, or
// if we're low on video frames, provided
// we've not decoded to the end of the video stream.
return ((IsAudioDecoding() && AudioDecodedUsecs() < aAudioUsecs) ||
(IsVideoDecoding() &&
static_cast<uint32_t>(VideoQueue().GetSize()) < LOW_VIDEO_FRAMES));
}
bool MediaDecoderStateMachine::OutOfDecodedAudio()
{
MOZ_ASSERT(OnTaskQueue());
return IsAudioDecoding() && !AudioQueue().IsFinished() &&
AudioQueue().GetSize() == 0 &&
!mMediaSink->HasUnplayedFrames(TrackInfo::kAudioTrack);
}
bool MediaDecoderStateMachine::HasLowUndecodedData()
{
MOZ_ASSERT(OnTaskQueue());
return HasLowUndecodedData(mLowDataThresholdUsecs);
}
bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
{
MOZ_ASSERT(OnTaskQueue());
NS_ASSERTION(mState >= DECODER_STATE_DECODING && !IsDecodingFirstFrame(),
"Must have loaded first frame for mBuffered to be valid");
// If we don't have a duration, mBuffered is probably not going to have
// a useful buffered range. Return false here so that we don't get stuck in
// buffering mode for live streams.
if (Duration().IsInfinite()) {
return false;
}
if (mBuffered.Ref().IsInvalid()) {
return false;
}
int64_t endOfDecodedVideoData = INT64_MAX;
if (HasVideo() && !VideoQueue().AtEndOfStream()) {
endOfDecodedVideoData = VideoQueue().Peek() ? VideoQueue().Peek()->GetEndTime() : VideoEndTime();
}
int64_t endOfDecodedAudioData = INT64_MAX;
if (HasAudio() && !AudioQueue().AtEndOfStream()) {
endOfDecodedAudioData = mDecodedAudioEndTime;
}
int64_t endOfDecodedData = std::min(endOfDecodedVideoData, endOfDecodedAudioData);
if (Duration().ToMicroseconds() < endOfDecodedData) {
// Our duration is not up to date. No point buffering.
return false;
}
media::TimeInterval interval(media::TimeUnit::FromMicroseconds(endOfDecodedData),
media::TimeUnit::FromMicroseconds(std::min(endOfDecodedData + aUsecs, Duration().ToMicroseconds())));
return endOfDecodedData != INT64_MAX && !mBuffered.Ref().Contains(interval);
}
void
MediaDecoderStateMachine::DecodeError()
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
// Already shutdown.
return;
}
// Change state to error, which will cause the state machine to wait until
// the MediaDecoder shuts it down.
SetState(DECODER_STATE_ERROR);
ScheduleStateMachine();
DECODER_WARN("Decode error, changed state to ERROR");
// MediaDecoder::DecodeError notifies the owner, and then shuts down the state
// machine.
mOnPlaybackEvent.Notify(MediaEventType::DecodeError);
}
void
MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
mMetadataRequest.Complete();
if (mPendingDormant) {
SetDormant(mPendingDormant.ref());
return;
}
// Set mode to PLAYBACK after reading metadata.
mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
mInfo = aMetadata->mInfo;
mMetadataTags = aMetadata->mTags.forget();
RefPtr<MediaDecoderStateMachine> self = this;
// Set up the start time rendezvous if it doesn't already exist (which is
// generally the case, unless we're coming out of dormant mode).
if (!mStartTimeRendezvous) {
mStartTimeRendezvous = new StartTimeRendezvous(OwnerThread(), HasAudio(), HasVideo(),
mReader->ForceZeroStartTime() || IsRealTime());
mStartTimeRendezvous->AwaitStartTime()->Then(OwnerThread(), __func__,
[self] () -> void {
NS_ENSURE_TRUE_VOID(!self->IsShutdown());
self->mReader->DispatchSetStartTime(self->StartTime());
},
[] () -> void { NS_WARNING("Setting start time on reader failed"); }
);
}
if (mInfo.mMetadataDuration.isSome()) {
RecomputeDuration();
} else if (mInfo.mUnadjustedMetadataEndTime.isSome()) {
mStartTimeRendezvous->AwaitStartTime()->Then(OwnerThread(), __func__,
[self] () -> void {
NS_ENSURE_TRUE_VOID(!self->IsShutdown());
TimeUnit unadjusted = self->mInfo.mUnadjustedMetadataEndTime.ref();
TimeUnit adjustment = TimeUnit::FromMicroseconds(self->StartTime());
self->mInfo.mMetadataDuration.emplace(unadjusted - adjustment);
self->RecomputeDuration();
}, [] () -> void { NS_WARNING("Adjusting metadata end time failed"); }
);
}
if (HasVideo()) {
DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
mReader->IsAsync(),
mReader->VideoIsHardwareAccelerated(),
GetAmpleVideoFrames());
}
// In general, we wait until we know the duration before notifying the decoder.
// However, we notify unconditionally in this case without waiting for the start
// time, since the caller might be waiting on metadataloaded to be fired before
// feeding in the CDM, which we need to decode the first frame (and
// thus get the metadata). We could fix this if we could compute the start
// time by demuxing without necessaring decoding.
bool waitingForCDM =
#ifdef MOZ_EME
mInfo.IsEncrypted() && !mCDMProxy;
#else
false;
#endif
mNotifyMetadataBeforeFirstFrame = mDuration.Ref().isSome() || waitingForCDM;
if (mNotifyMetadataBeforeFirstFrame) {
EnqueueLoadedMetadataEvent();
}
if (waitingForCDM) {
// Metadata parsing was successful but we're still waiting for CDM caps
// to become available so that we can build the correct decryptor/decoder.
SetState(DECODER_STATE_WAIT_FOR_CDM);
return;
}
StartDecoding();
ScheduleStateMachine();
}
void
MediaDecoderStateMachine::OnMetadataNotRead(ReadMetadataFailureReason aReason)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
mMetadataRequest.Complete();
DECODER_WARN("Decode metadata failed, shutting down decoder");
DecodeError();
}
void
MediaDecoderStateMachine::EnqueueLoadedMetadataEvent()
{
MOZ_ASSERT(OnTaskQueue());
MediaDecoderEventVisibility visibility =
mSentLoadedMetadataEvent ? MediaDecoderEventVisibility::Suppressed
: MediaDecoderEventVisibility::Observable;
mMetadataLoadedEvent.Notify(nsAutoPtr<MediaInfo>(new MediaInfo(mInfo)),
Move(mMetadataTags),
Move(visibility));
mSentLoadedMetadataEvent = true;
}
void
MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent()
{
MOZ_ASSERT(OnTaskQueue());
// Track value of mSentFirstFrameLoadedEvent from before updating it
bool firstFrameBeenLoaded = mSentFirstFrameLoadedEvent;
mSentFirstFrameLoadedEvent = true;
RefPtr<MediaDecoderStateMachine> self = this;
mBufferedUpdateRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::UpdateBufferedWithPromise)
->Then(OwnerThread(),
__func__,
// Resolve
[self, firstFrameBeenLoaded]() {
self->mBufferedUpdateRequest.Complete();
MediaDecoderEventVisibility visibility =
firstFrameBeenLoaded ? MediaDecoderEventVisibility::Suppressed
: MediaDecoderEventVisibility::Observable;
self->mFirstFrameLoadedEvent.Notify(nsAutoPtr<MediaInfo>(new MediaInfo(self->mInfo)),
Move(visibility));
},
// Reject
[]() { MOZ_CRASH("Should not reach"); }));
}
bool
MediaDecoderStateMachine::IsDecodingFirstFrame()
{
return mState == DECODER_STATE_DECODING && mDecodingFirstFrame;
}
void
MediaDecoderStateMachine::FinishDecodeFirstFrame()
{
MOZ_ASSERT(OnTaskQueue());
DECODER_LOG("FinishDecodeFirstFrame");
if (!IsRealTime() && !mSentFirstFrameLoadedEvent) {
mMediaSink->Redraw();
}
// If we don't know the duration by this point, we assume infinity, per spec.
if (mDuration.Ref().isNothing()) {
mDuration = Some(TimeUnit::FromInfinity());
}
DECODER_LOG("Media duration %lld, "
"transportSeekable=%d, mediaSeekable=%d",
Duration().ToMicroseconds(), mResource->IsTransportSeekable(), mMediaSeekable.Ref());
// Get potentially updated metadata
mReader->ReadUpdatedMetadata(&mInfo);
if (!mNotifyMetadataBeforeFirstFrame) {
// If we didn't have duration and/or start time before, we should now.
EnqueueLoadedMetadataEvent();
}
EnqueueFirstFrameLoadedEvent();
mDecodingFirstFrame = false;
}
void
MediaDecoderStateMachine::SeekCompleted()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mState == DECODER_STATE_SEEKING);
int64_t seekTime = mCurrentSeek.mTarget.GetTime().ToMicroseconds();
int64_t newCurrentTime = seekTime;
// Setup timestamp state.
RefPtr<MediaData> video = VideoQueue().PeekFront();
if (seekTime == Duration().ToMicroseconds()) {
newCurrentTime = seekTime;
} else if (HasAudio()) {
MediaData* audio = AudioQueue().PeekFront();
// Though we adjust the newCurrentTime in audio-based, and supplemented
// by video. For better UX, should NOT bind the slide position to
// the first audio data timestamp directly.
// While seeking to a position where there's only either audio or video, or
// seeking to a position lies before audio or video, we need to check if
// seekTime is bounded in suitable duration. See Bug 1112438.
int64_t audioStart = audio ? audio->mTime : seekTime;
// We only pin the seek time to the video start time if the video frame
// contains the seek time.
if (video && video->mTime <= seekTime && video->GetEndTime() > seekTime) {
newCurrentTime = std::min(audioStart, video->mTime);
} else {
newCurrentTime = audioStart;
}
} else {
newCurrentTime = video ? video->mTime : seekTime;
}
if (mDecodingFirstFrame) {
// We were resuming from dormant, or initiated a seek early.
// We can fire loadeddata now.
FinishDecodeFirstFrame();
}
// Change state to DECODING or COMPLETED now. SeekingStopped will
// call MediaDecoderStateMachine::Seek to reset our state to SEEKING
// if we need to seek again.
bool isLiveStream = mResource->IsLiveStream();
if (mPendingSeek.Exists()) {
// A new seek target came in while we were processing the old one. No rest
// for the seeking.
DECODER_LOG("A new seek came along while we were finishing the old one - staying in SEEKING");
SetState(DECODER_STATE_SEEKING);
} else if (GetMediaTime() == Duration().ToMicroseconds() && !isLiveStream) {
// Seeked to end of media, move to COMPLETED state. Note we don't do
// this when playing a live stream, since the end of media will advance
// once we download more data!
DECODER_LOG("Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
// Explicitly set our state so we don't decode further, and so
// we report playback ended to the media element.
SetState(DECODER_STATE_COMPLETED);
DispatchDecodeTasksIfNeeded();
} else {
DECODER_LOG("Changed state from SEEKING (to %lld) to DECODING", seekTime);
StartDecoding();
}
// Ensure timestamps are up to date.
UpdatePlaybackPositionInternal(newCurrentTime);
// Try to decode another frame to detect if we're at the end...
DECODER_LOG("Seek completed, mCurrentPosition=%lld", mCurrentPosition.Ref());
// Reset quick buffering status. This ensures that if we began the
// seek while quick-buffering, we won't bypass quick buffering mode
// if we need to buffer after the seek.
mQuickBuffering = false;
mCurrentSeek.Resolve(mState == DECODER_STATE_COMPLETED, __func__);
ScheduleStateMachine();
if (video) {
mMediaSink->Redraw();
mOnPlaybackEvent.Notify(MediaEventType::Invalidate);
}
}
RefPtr<ShutdownPromise>
MediaDecoderStateMachine::BeginShutdown()
{
return InvokeAsync(OwnerThread(), this, __func__,
&MediaDecoderStateMachine::Shutdown);
}
RefPtr<ShutdownPromise>
MediaDecoderStateMachine::FinishShutdown()
{
MOZ_ASSERT(OnTaskQueue());
// The reader's listeners hold references to the state machine,
// creating a cycle which keeps the state machine and its shared
// thread pools alive. So break it here.
// Prevent dangling pointers by disconnecting the listeners.
mAudioQueueListener.Disconnect();
mVideoQueueListener.Disconnect();
mMetadataManager.Disconnect();
// Disconnect canonicals and mirrors before shutting down our task queue.
mBuffered.DisconnectIfConnected();
mEstimatedDuration.DisconnectIfConnected();
mExplicitDuration.DisconnectIfConnected();
mPlayState.DisconnectIfConnected();
mNextPlayState.DisconnectIfConnected();
mLogicallySeeking.DisconnectIfConnected();
mVolume.DisconnectIfConnected();
mLogicalPlaybackRate.DisconnectIfConnected();
mPreservesPitch.DisconnectIfConnected();
mSameOriginMedia.DisconnectIfConnected();
mMediaPrincipalHandle.DisconnectIfConnected();
mPlaybackBytesPerSecond.DisconnectIfConnected();
mPlaybackRateReliable.DisconnectIfConnected();
mDecoderPosition.DisconnectIfConnected();
mMediaSeekable.DisconnectIfConnected();
mDuration.DisconnectAll();
mIsShutdown.DisconnectAll();
mNextFrameStatus.DisconnectAll();
mCurrentPosition.DisconnectAll();
mPlaybackOffset.DisconnectAll();
mIsAudioDataAudible.DisconnectAll();
// Shut down the watch manager before shutting down our task queue.
mWatchManager.Shutdown();
MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
"How did we escape from the shutdown state?");
DECODER_LOG("Shutting down state machine task queue");
return OwnerThread()->BeginShutdown();
}
nsresult MediaDecoderStateMachine::RunStateMachine()
{
MOZ_ASSERT(OnTaskQueue());
mDelayedScheduler.Reset(); // Must happen on state machine task queue.
mDispatchedStateMachine = false;
MediaResource* resource = mResource;
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
switch (mState) {
case DECODER_STATE_ERROR:
case DECODER_STATE_SHUTDOWN:
case DECODER_STATE_DORMANT:
case DECODER_STATE_WAIT_FOR_CDM:
case DECODER_STATE_DECODING_METADATA:
return NS_OK;
case DECODER_STATE_DECODING: {
if (IsDecodingFirstFrame()) {
// We haven't completed decoding our first frames, we can't start
// playback yet.
return NS_OK;
}
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying())
{
// We're playing, but the element/decoder is in paused state. Stop
// playing!
StopPlayback();
}
// Start playback if necessary so that the clock can be properly queried.
MaybeStartPlayback();
UpdatePlaybackPositionPeriodically();
NS_ASSERTION(!IsPlaying() ||
mLogicallySeeking ||
IsStateMachineScheduled(),
"Must have timer scheduled");
return NS_OK;
}
case DECODER_STATE_BUFFERING: {
TimeStamp now = TimeStamp::Now();
NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
// With buffering heuristics we will remain in the buffering state if
// we've not decoded enough data to begin playback, or if we've not
// downloaded a reasonable amount of data inside our buffering time.
if (mReader->UseBufferingHeuristics()) {
TimeDuration elapsed = now - mBufferingStart;
bool isLiveStream = resource->IsLiveStream();
if ((isLiveStream || !CanPlayThrough()) &&
elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
(mQuickBuffering ? HasLowDecodedData(mQuickBufferingLowDataThresholdUsecs)
: HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
mResource->IsExpectingMoreData())
{
DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
(mQuickBuffering ? "(quick exit)" : ""));
ScheduleStateMachineIn(USECS_PER_S);
return NS_OK;
}
} else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
"Don't yet have a strategy for non-heuristic + non-WaitForData");
DispatchDecodeTasksIfNeeded();
MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mAudioDataRequest.Exists() || mAudioWaitRequest.Exists());
MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mVideoDataRequest.Exists() || mVideoWaitRequest.Exists());
DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
"mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
OutOfDecodedAudio(), AudioRequestStatus(),
OutOfDecodedVideo(), VideoRequestStatus());
return NS_OK;
}
DECODER_LOG("Changed state from BUFFERING to DECODING");
DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
StartDecoding();
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
return NS_OK;
}
case DECODER_STATE_SEEKING: {
if (mPendingSeek.Exists()) {
InitiateSeek();
}
return NS_OK;
}
case DECODER_STATE_COMPLETED: {
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying()) {
StopPlayback();
}
// Play the remaining media. We want to run AdvanceFrame() at least
// once to ensure the current playback position is advanced to the
// end of the media, and so that we update the readyState.
if ((HasVideo() && !mVideoCompleted) ||
(HasAudio() && !mAudioCompleted)) {
// Start playback if necessary to play the remaining media.
MaybeStartPlayback();
UpdatePlaybackPositionPeriodically();
NS_ASSERTION(!IsPlaying() ||
mLogicallySeeking ||
IsStateMachineScheduled(),
"Must have timer scheduled");
return NS_OK;
}
// StopPlayback in order to reset the IsPlaying() state so audio
// is restarted correctly.
StopPlayback();
if (mState != DECODER_STATE_COMPLETED) {
// While we're presenting a frame we can change state. Whatever changed
// our state should have scheduled another state machine run.
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
return NS_OK;
}
if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
!mSentPlaybackEndedEvent)
{
int64_t clockTime = std::max(AudioEndTime(), VideoEndTime());
clockTime = std::max(int64_t(0), std::max(clockTime, Duration().ToMicroseconds()));
UpdatePlaybackPosition(clockTime);
// Ensure readyState is updated before firing the 'ended' event.
UpdateNextFrameStatus();
mOnPlaybackEvent.Notify(MediaEventType::PlaybackEnded);
mSentPlaybackEndedEvent = true;
// MediaSink::GetEndTime() must be called before stopping playback.
StopMediaSink();
}
return NS_OK;
}
}
return NS_OK;
}
void
MediaDecoderStateMachine::Reset()
{
MOZ_ASSERT(OnTaskQueue());
DECODER_LOG("MediaDecoderStateMachine::Reset");
// We should be resetting because we're seeking, shutting down, or entering
// dormant state. We could also be in the process of going dormant, and have
// just switched to exiting dormant before we finished entering dormant,
// hence the DECODING_NONE case below.
MOZ_ASSERT(IsShutdown() ||
mState == DECODER_STATE_SEEKING ||
mState == DECODER_STATE_DORMANT);
// Stop the audio thread. Otherwise, MediaSink might be accessing AudioQueue
// outside of the decoder monitor while we are clearing the queue and causes
// crash for no samples to be popped.
StopMediaSink();
mDecodedVideoEndTime = 0;
mDecodedAudioEndTime = 0;
mAudioCompleted = false;
mVideoCompleted = false;
AudioQueue().Reset();
VideoQueue().Reset();
mFirstVideoFrameAfterSeek = nullptr;
mDropAudioUntilNextDiscontinuity = true;
mDropVideoUntilNextDiscontinuity = true;
mMetadataRequest.DisconnectIfExists();
mAudioDataRequest.DisconnectIfExists();
mAudioWaitRequest.DisconnectIfExists();
mVideoDataRequest.DisconnectIfExists();
mVideoWaitRequest.DisconnectIfExists();
mSeekRequest.DisconnectIfExists();
mPlaybackOffset = 0;
nsCOMPtr<nsIRunnable> resetTask =
NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode);
DecodeTaskQueue()->Dispatch(resetTask.forget());
}
int64_t
MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const
{
MOZ_ASSERT(OnTaskQueue());
int64_t clockTime = mMediaSink->GetPosition(aTimeStamp);
NS_ASSERTION(GetMediaTime() <= clockTime, "Clock should go forwards.");
return clockTime;
}
void
MediaDecoderStateMachine::UpdatePlaybackPositionPeriodically()
{
MOZ_ASSERT(OnTaskQueue());
if (!IsPlaying() || mLogicallySeeking) {
return;
}
if (mAudioCaptured) {
DiscardStreamData();
}
// Cap the current time to the larger of the audio and video end time.
// This ensures that if we're running off the system clock, we don't
// advance the clock to after the media end time.
if (VideoEndTime() != -1 || AudioEndTime() != -1) {
const int64_t clockTime = GetClock();
// Skip frames up to the frame at the playback position, and figure out
// the time remaining until it's time to display the next frame and drop
// the current frame.
NS_ASSERTION(clockTime >= 0, "Should have positive clock time.");
// These will be non -1 if we've displayed a video frame, or played an audio frame.
int64_t t = std::min(clockTime, std::max(VideoEndTime(), AudioEndTime()));
// FIXME: Bug 1091422 - chained ogg files hit this assertion.
//MOZ_ASSERT(t >= GetMediaTime());
if (t > GetMediaTime()) {
UpdatePlaybackPosition(t);
}
}
// Note we have to update playback position before releasing the monitor.
// Otherwise, MediaDecoder::AddOutputStream could kick in when we are outside
// the monitor and get a staled value from GetCurrentTimeUs() which hits the
// assertion in GetClock().
int64_t delay = std::max<int64_t>(1, AUDIO_DURATION_USECS / mPlaybackRate);
ScheduleStateMachineIn(delay);
}
nsresult
MediaDecoderStateMachine::DropVideoUpToSeekTarget(MediaData* aSample)
{
MOZ_ASSERT(OnTaskQueue());
RefPtr<VideoData> video(aSample->As<VideoData>());
MOZ_ASSERT(video);
DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld]",
video->mTime, video->GetEndTime());
MOZ_ASSERT(mCurrentSeek.Exists());
const int64_t target = mCurrentSeek.mTarget.GetTime().ToMicroseconds();
// If the frame end time is less than the seek target, we won't want
// to display this frame after the seek, so discard it.
if (target >= video->GetEndTime()) {
DECODER_LOG("DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld",
video->mTime, video->GetEndTime(), target);
mFirstVideoFrameAfterSeek = video;
} else {
if (target >= video->mTime && video->GetEndTime() >= target) {
// The seek target lies inside this frame's time slice. Adjust the frame's
// start time to match the seek target. We do this by replacing the
// first frame with a shallow copy which has the new timestamp.
RefPtr<VideoData> temp = VideoData::ShallowCopyUpdateTimestamp(video, target);
video = temp;
}
mFirstVideoFrameAfterSeek = nullptr;
DECODER_LOG("DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
video->mTime, video->GetEndTime(), target);
PushFront(video, MediaData::VIDEO_DATA);
}
return NS_OK;
}
nsresult
MediaDecoderStateMachine::DropAudioUpToSeekTarget(MediaData* aSample)
{
MOZ_ASSERT(OnTaskQueue());
RefPtr<AudioData> audio(aSample->As<AudioData>());
MOZ_ASSERT(audio &&
mCurrentSeek.Exists() &&
mCurrentSeek.mTarget.IsAccurate());
CheckedInt64 sampleDuration =
FramesToUsecs(audio->mFrames, mInfo.mAudio.mRate);
if (!sampleDuration.isValid()) {
return NS_ERROR_FAILURE;
}
if (audio->mTime + sampleDuration.value() <= mCurrentSeek.mTarget.GetTime().ToMicroseconds()) {
// Our seek target lies after the frames in this AudioData. Don't
// push it onto the audio queue, and keep decoding forwards.
return NS_OK;
}
if (audio->mTime > mCurrentSeek.mTarget.GetTime().ToMicroseconds()) {
// The seek target doesn't lie in the audio block just after the last
// audio frames we've seen which were before the seek target. This
// could have been the first audio data we've seen after seek, i.e. the
// seek terminated after the seek target in the audio stream. Just
// abort the audio decode-to-target, the state machine will play
// silence to cover the gap. Typically this happens in poorly muxed
// files.
DECODER_WARN("Audio not synced after seek, maybe a poorly muxed file?");
Push(audio, MediaData::AUDIO_DATA);
return NS_OK;
}
// The seek target lies somewhere in this AudioData's frames, strip off
// any frames which lie before the seek target, so we'll begin playback
// exactly at the seek target.
NS_ASSERTION(mCurrentSeek.mTarget.GetTime().ToMicroseconds() >= audio->mTime,
"Target must at or be after data start.");
NS_ASSERTION(mCurrentSeek.mTarget.GetTime().ToMicroseconds() < audio->mTime + sampleDuration.value(),
"Data must end after target.");
CheckedInt64 framesToPrune =
UsecsToFrames(mCurrentSeek.mTarget.GetTime().ToMicroseconds() - audio->mTime, mInfo.mAudio.mRate);
if (!framesToPrune.isValid()) {
return NS_ERROR_FAILURE;
}
if (framesToPrune.value() > audio->mFrames) {
// We've messed up somehow. Don't try to trim frames, the |frames|
// variable below will overflow.
DECODER_WARN("Can't prune more frames that we have!");
return NS_ERROR_FAILURE;
}
uint32_t frames = audio->mFrames - static_cast<uint32_t>(framesToPrune.value());
uint32_t channels = audio->mChannels;
auto audioData = MakeUnique<AudioDataValue[]>(frames * channels);
memcpy(audioData.get(),
audio->mAudioData.get() + (framesToPrune.value() * channels),
frames * channels * sizeof(AudioDataValue));
CheckedInt64 duration = FramesToUsecs(frames, mInfo.mAudio.mRate);
if (!duration.isValid()) {
return NS_ERROR_FAILURE;
}
RefPtr<AudioData> data(new AudioData(audio->mOffset,
mCurrentSeek.mTarget.GetTime().ToMicroseconds(),
duration.value(),
frames,
Move(audioData),
channels,
audio->mRate));
PushFront(data, MediaData::AUDIO_DATA);
return NS_OK;
}
void MediaDecoderStateMachine::UpdateNextFrameStatus()
{
MOZ_ASSERT(OnTaskQueue());
MediaDecoderOwner::NextFrameStatus status;
const char* statusString;
if (mState <= DECODER_STATE_WAIT_FOR_CDM || IsDecodingFirstFrame()) {
status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
statusString = "NEXT_FRAME_UNAVAILABLE";
} else if (IsBuffering()) {
status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING;
statusString = "NEXT_FRAME_UNAVAILABLE_BUFFERING";
} else if (IsSeeking()) {
status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING;
statusString = "NEXT_FRAME_UNAVAILABLE_SEEKING";
} else if (HaveNextFrameData()) {
status = MediaDecoderOwner::NEXT_FRAME_AVAILABLE;
statusString = "NEXT_FRAME_AVAILABLE";
} else {
status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
statusString = "NEXT_FRAME_UNAVAILABLE";
}
if (status != mNextFrameStatus) {
DECODER_LOG("Changed mNextFrameStatus to %s", statusString);
}
mNextFrameStatus = status;
}
bool MediaDecoderStateMachine::JustExitedQuickBuffering()
{
MOZ_ASSERT(OnTaskQueue());
return !mDecodeStartTime.IsNull() &&
mQuickBuffering &&
(TimeStamp::Now() - mDecodeStartTime) < TimeDuration::FromMicroseconds(QUICK_BUFFER_THRESHOLD_USECS);
}
bool
MediaDecoderStateMachine::CanPlayThrough()
{
MOZ_ASSERT(OnTaskQueue());
return IsRealTime() || GetStatistics().CanPlayThrough();
}
MediaStatistics
MediaDecoderStateMachine::GetStatistics()
{
MOZ_ASSERT(OnTaskQueue());
MediaStatistics result;
result.mDownloadRate = mResource->GetDownloadRate(&result.mDownloadRateReliable);
result.mDownloadPosition = mResource->GetCachedDataEnd(mDecoderPosition);
result.mTotalBytes = mResource->GetLength();
result.mPlaybackRate = mPlaybackBytesPerSecond;
result.mPlaybackRateReliable = mPlaybackRateReliable;
result.mDecoderPosition = mDecoderPosition;
result.mPlaybackPosition = mPlaybackOffset;
return result;
}
void MediaDecoderStateMachine::StartBuffering()
{
MOZ_ASSERT(OnTaskQueue());
if (mState != DECODER_STATE_DECODING) {
// We only move into BUFFERING state if we're actually decoding.
// If we're currently doing something else, we don't need to buffer,
// and more importantly, we shouldn't overwrite mState to interrupt
// the current operation, as that could leave us in an inconsistent
// state!
return;
}
if (IsPlaying()) {
StopPlayback();
}
TimeDuration decodeDuration = TimeStamp::Now() - mDecodeStartTime;
// Go into quick buffering mode provided we've not just left buffering using
// a "quick exit". This stops us flip-flopping between playing and buffering
// when the download speed is similar to the decode speed.
mQuickBuffering =
!JustExitedQuickBuffering() &&
decodeDuration < UsecsToDuration(QUICK_BUFFER_THRESHOLD_USECS);
mBufferingStart = TimeStamp::Now();
SetState(DECODER_STATE_BUFFERING);
DECODER_LOG("Changed state from DECODING to BUFFERING, decoded for %.3lfs",
decodeDuration.ToSeconds());
MediaStatistics stats = GetStatistics();
DECODER_LOG("Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)");
}
void
MediaDecoderStateMachine::ScheduleStateMachine()
{
MOZ_ASSERT(OnTaskQueue());
if (mDispatchedStateMachine) {
return;
}
mDispatchedStateMachine = true;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::RunStateMachine);
OwnerThread()->Dispatch(task.forget());
}
void
MediaDecoderStateMachine::ScheduleStateMachineIn(int64_t aMicroseconds)
{
MOZ_ASSERT(OnTaskQueue()); // mDelayedScheduler.Ensure() may Disconnect()
// the promise, which must happen on the state
// machine task queue.
MOZ_ASSERT(aMicroseconds > 0);
if (mDispatchedStateMachine) {
return;
}
// Real-time weirdness.
if (IsRealTime()) {
aMicroseconds = std::min(aMicroseconds, int64_t(40000));
}
TimeStamp now = TimeStamp::Now();
TimeStamp target = now + TimeDuration::FromMicroseconds(aMicroseconds);
SAMPLE_LOG("Scheduling state machine for %lf ms from now", (target - now).ToMilliseconds());
RefPtr<MediaDecoderStateMachine> self = this;
mDelayedScheduler.Ensure(target, [self] () {
self->OnDelayedSchedule();
}, [self] () {
self->NotReached();
});
}
bool MediaDecoderStateMachine::OnTaskQueue() const
{
return OwnerThread()->IsCurrentThreadIn();
}
bool MediaDecoderStateMachine::IsStateMachineScheduled() const
{
MOZ_ASSERT(OnTaskQueue());
return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
}
void
MediaDecoderStateMachine::LogicalPlaybackRateChanged()
{
MOZ_ASSERT(OnTaskQueue());
if (mLogicalPlaybackRate == 0) {
// This case is handled in MediaDecoder by pausing playback.
return;
}
mPlaybackRate = mLogicalPlaybackRate;
mMediaSink->SetPlaybackRate(mPlaybackRate);
ScheduleStateMachine();
}
void MediaDecoderStateMachine::PreservesPitchChanged()
{
MOZ_ASSERT(OnTaskQueue());
mMediaSink->SetPreservesPitch(mPreservesPitch);
}
bool MediaDecoderStateMachine::IsShutdown()
{
MOZ_ASSERT(OnTaskQueue());
return mIsShutdown;
}
int64_t
MediaDecoderStateMachine::AudioEndTime() const
{
MOZ_ASSERT(OnTaskQueue());
if (mMediaSink->IsStarted()) {
return mMediaSink->GetEndTime(TrackInfo::kAudioTrack);
}
MOZ_ASSERT(!HasAudio());
return -1;
}
int64_t
MediaDecoderStateMachine::VideoEndTime() const
{
MOZ_ASSERT(OnTaskQueue());
if (mMediaSink->IsStarted()) {
return mMediaSink->GetEndTime(TrackInfo::kVideoTrack);
}
return -1;
}
void
MediaDecoderStateMachine::OnMediaSinkVideoComplete()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mInfo.HasVideo());
VERBOSE_LOG("[%s]", __func__);
mMediaSinkVideoPromise.Complete();
mVideoCompleted = true;
ScheduleStateMachine();
}
void
MediaDecoderStateMachine::OnMediaSinkVideoError()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mInfo.HasVideo());
VERBOSE_LOG("[%s]", __func__);
mMediaSinkVideoPromise.Complete();
mVideoCompleted = true;
if (HasAudio()) {
return;
}
DecodeError();
}
void MediaDecoderStateMachine::OnMediaSinkAudioComplete()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mInfo.HasAudio());
VERBOSE_LOG("[%s]", __func__);
mMediaSinkAudioPromise.Complete();
mAudioCompleted = true;
// To notify PlaybackEnded as soon as possible.
ScheduleStateMachine();
}
void MediaDecoderStateMachine::OnMediaSinkAudioError()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mInfo.HasAudio());
VERBOSE_LOG("[%s]", __func__);
mMediaSinkAudioPromise.Complete();
mAudioCompleted = true;
// Make the best effort to continue playback when there is video.
if (HasVideo()) {
return;
}
// Otherwise notify media decoder/element about this error for it makes
// no sense to play an audio-only file without sound output.
DecodeError();
}
#ifdef MOZ_EME
void
MediaDecoderStateMachine::OnCDMProxyReady(RefPtr<CDMProxy> aProxy)
{
MOZ_ASSERT(OnTaskQueue());
mCDMProxyPromise.Complete();
mCDMProxy = aProxy;
mReader->SetCDMProxy(aProxy);
if (mState == DECODER_STATE_WAIT_FOR_CDM) {
StartDecoding();
}
}
void
MediaDecoderStateMachine::OnCDMProxyNotReady()
{
MOZ_ASSERT(OnTaskQueue());
mCDMProxyPromise.Complete();
}
#endif
void
MediaDecoderStateMachine::SetAudioCaptured(bool aCaptured)
{
MOZ_ASSERT(OnTaskQueue());
if (aCaptured == mAudioCaptured) {
return;
}
// Backup current playback parameters.
MediaSink::PlaybackParams params = mMediaSink->GetPlaybackParams();
// Stop and shut down the existing sink.
StopMediaSink();
mMediaSink->Shutdown();
// Create a new sink according to whether audio is captured.
mMediaSink = CreateMediaSink(aCaptured);
// Restore playback parameters.
mMediaSink->SetPlaybackParams(params);
// We don't need to call StartMediaSink() here because IsPlaying() is now
// always in sync with the playing state of MediaSink. It will be started in
// MaybeStartPlayback() in the next cycle if necessary.
mAudioCaptured = aCaptured;
ScheduleStateMachine();
// Don't buffer as much when audio is captured because we don't need to worry
// about high latency audio devices.
mAmpleAudioThresholdUsecs = mAudioCaptured ?
detail::AMPLE_AUDIO_USECS / 2 :
detail::AMPLE_AUDIO_USECS;
if (mIsAudioPrerolling && DonePrerollingAudio()) {
StopPrerollingAudio();
}
}
uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const
{
MOZ_ASSERT(OnTaskQueue());
return (mReader->IsAsync() && mReader->VideoIsHardwareAccelerated())
? std::max<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
: std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
}
void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
bool aFinishWhenEnded)
{
MOZ_ASSERT(NS_IsMainThread());
DECODER_LOG("AddOutputStream aStream=%p!", aStream);
mOutputStreamManager->Add(aStream, aFinishWhenEnded);
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<bool>(
this, &MediaDecoderStateMachine::SetAudioCaptured, true);
OwnerThread()->Dispatch(r.forget());
}
void MediaDecoderStateMachine::RemoveOutputStream(MediaStream* aStream)
{
MOZ_ASSERT(NS_IsMainThread());
DECODER_LOG("RemoveOutputStream=%p!", aStream);
mOutputStreamManager->Remove(aStream);
if (mOutputStreamManager->IsEmpty()) {
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<bool>(
this, &MediaDecoderStateMachine::SetAudioCaptured, false);
OwnerThread()->Dispatch(r.forget());
}
}
} // namespace mozilla
// avoid redefined macro in unified build
#undef LOG
#undef DECODER_LOG
#undef VERBOSE_LOG
#undef DECODER_WARN
#undef DECODER_WARN_HELPER
#undef NS_DispatchToMainThread