mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
8637eba4b3
- remove poisondata stuff (cf5b5d29e5) - Bug 1218488 - clarify buffer ownership for nsICanvasRenderingContextInternal::GetBuffer; r=Bas,baku (dba4272b73) - Bug 1167215 - Re-apply CompositeUntil calls when we get a new batch of textures. r=roc (d57a8ac9e9) - Bug 1220923 - Make nsIntRegion a typedef for IntRegionTyped<UnknownUnits>. r=nical (a5f4e1e283) - Bug 1220898 - Make nsIntMargin a typedef for mozilla::gfx::IntMargin. r=nical (8bdb2f191a) - Bug 1205913 - Differentiate YCbCr, NV12 and RGB textures when drawing layer borders. r=nical (ff5d3dc7df) - Bug 1203376 - Honor filter region settings for lighting filters. r=mstange (8d0565e99a) - Bug 1220512 - ensure next frame status is updated before notifying MediaDecoder::PlaybackEnded. See bug 1220512 comment 1 for the detail. r=cpearce. (c2f434b5d4) - Bug 1207964 - Remove workaround from bug 1080461. r=jwwang (19bff98c14) - Bug 1199904: Only start decoding ahead after explicitly requesting data. r=gerald (8712637b49) - Bug 1197664: Report the total number of decoded frames. CLOSED TREE r=kentuckyfriedtakahe (422827d1af) - Bug 1178596: Reset frame size queue after flushing. r=cpearce (dd40ac4e6b) - Bug 1205911: P1. Cancel pending demux request when searching for next keyframe. r=edwin (8a03e6904f) - Bug 1188313: P1. Attempt to seek audio near video. r=cpearce (a8b6465d97) - Bug 1205911: P2. Ensure demuxer is reset before performing a seek. r=edwin (e93534a1d1) - bit of Bug 1185886: P2 (c2e89be4a3) - bit of Bug 1211443 (98fbb4a4a2) - Bug 1089586: Abort pending seeks. r=jwwang (e81f453204) - Bug 1195094: P1. Properly detects SPS changes for decoders requiring Annex B. r=cpearce (976febcd7c) - Bug 1195094: P2. Ensure TrackInfo object passed to constructor is never modified. r=cpearce (d5c15b2368) - Bug 1218577: Use only Blank PDM if enabled. r=kamidphish (a7e06549cc) - Bug 1210231 - Enable unencrypted <video> playback using Adobe's GMP for decoding. r=jya (7f80702b89) - Bug 1214932 - Add media.wmf.enabled pref. r=jya (8c183c0dd1) - Bug 1213173: Always use FFmpeg regardless of version. r=kentuckyfriedtakahe (e5af2c0c91) - Bug 1211787 - Improve the accuracy of MediaDecoderStateMachine::GetDecodedAudioDuration(). r=roc. (16d755c555) - Bug 1193614 - Schedule State Machine when VideoQueue() is low. r=cpearce (fe608da5fd) - Bug 1219984. Part 1 - remove EventPassMode::Both. In order to support multiple arguments, all arguments must be either moved or copied. r=kinetik. (2ee049008c) - Bug 1219984. Part 2 - add support for multiple arguments. r=kinetik. (1c70d5d69b) - Bug 1219974 - Add DisconnectIfExists() to MediaEventListener. r=kinetik. (43f6ae90b0) - Bug 1192109 - Fix insufficient includes in MediaEventSource.h. r=kinetik. (09eca240a1) - mData, aData (5e87f70853) - Bug 1219330 - Handle PlanaYCbCrImage::SetData failure. r=jya, jesup (144f3c266f) - Bug 1222308. Assume frames that are very old will never be composited. r=nical (1da9be2527) - space (45b67caa85) - Bug 1182426 - Set PlanarYCbCrImage's size in VP8TrackEncoder GTest. r=roc (d70a4d20c4) - Bug 1217080. Move recycling functionality into RecyclingPlanarYCbCrImage. r=nical (e2b2f650d7) - Bug 1216644 - part 1 - simple s/nsAutoArrayPtr/UniquePtr/ changes in gfx/; r=jrmuizel (d19bc51f94) - Bug 1216644 - part 2 - make gfxFontEntry::mUVSData a UniquePtr; r=jrmuizel (605a1bc6b1) - Bug 1216644 - part 3 - make BufferRecycleBin store UniquePtrs; r=jrmuizel (7781281266) - Add an RAII class to lock and unlock textures. (bug 1222863 part 1, r=nical) (abdec485f3) - Use RAII for more TextureClient locking cases. (bug 1222863 part 2, r=nical) (b35486ed3d) - Bug 1212288 - Make ImageContainer::AllocateProducerID callable on all threads; r=roc (fc569b8e48) - Bug 1140947 - Correct some logging in SourceBufferResource.cpp. r=cajbir (fcbb38056c) - Bug 1199573: [MSE] Properly handle partial media header received prior a discontinuity. r=gerald (f1cda54fa3) - Bug 1213726 - Remove AbstractMediaDecoder::HasInitializationData(). r=kinetik. (62f09f816e) - Bug 1034081 - Never seek before startTime. r=rillian Only adjust seek target up to startTime (44139b20ec) - Bug 1196353 - Use standard Xiph extradata format to pass headers from demuxers to decoders. r=jya (41f8dee5af) - Bug 1202332 - XiphExtradataToHeaders miscalculates final header length. r=derf (eec20f90f0) - Bug 1208799: [webm] Use first track found. r=kinetik (7196355e31) - Bug 1190472 - part 1 - improve MediaRawDataQueue's reference-counting behavior; r=kinetik (47b066b8e8) - Bug 1190472 - part 2 - delete unused MediaRawDataQueue::Push method; r=kinetik (f8246efe1b) - Bug 1190472 - part 3 - optimize pushing an entire queue onto MediaRawDataQueue; r=kinetik (045b389bf8) - Bug 1213024 - Comment the structured clone reading and writing, r=fitzgen (1b64f9f502) - Bug 1201620 - Make SavedFrame stacks structured cloneable; r=sfink (87e9b0a04b) - Bug 1221456 - Avoid C4819 warning spam. r=glandium (3dde3cb10a) - Bug 1213752 - IonMonkey: MIPS: Enable MIPS64 support. r=glandium (36da4cbd99) - Bug 1214175 - Make hash table manipulation infallible in Shape::fixupGetterSetterForBarrier() r=terrence (98e5e601ec) - Bug 1215337 - Cache slotSpan(), r=terrence (3e992f9f06) - No Bug - Include jswin.h in jsobj.cpp to unbreak windows non-unified bustage. (r=terrence over IRC) (92cba3254f) - Bug 1052139 - Make prototype-setting first create Object.prototype so that subsequent prototype chain-splicing will work correctly. r=bz (34a8d00d9f) - Bug 1172076 - Improve js::DumpBacktrace to include raw frame pointers and types. r=jandem (f11eb1763b) - Bug 1052139 - Change the boolean constant controlling whether the global object prototype chain is immutable, to enable immutable-prototype enforcement generally. r=duh (b7a1124feb) - Bug 1215363 - Fix a couple of OOM handling issues and make JS_sprintf funcions crash when passed illegal format strings r=terrence (c778891dd3) - Bug 1211331 - Ensure that GC slices are terminated such that we can safely iterate the heap. r=terrence (440308a333) - Bug 1224048 - Use stable hashing for the temporary tables in the JSON parser; r=jonco (7d058f44c9) - Bug 1215678 - Nuke cross compartment wrappers if we fail to add them to the wrapper map r=terrence (8e9e535a11) - Bug 1200732 - Use stable hashing for AutoCycleDetectorSet; r=jonco (68237bdea4) - more poisondata removal (6479b20220) - Bug 1218488 - clarify buffer ownership for nsICanvasRenderingContextInternal::GetBuffer; r=Bas,baku (ae89d929b2) - Bug 1207378 (Part 1) - Add support for a frame rect to Downscaler. r=tn (234866c32f) - Bug 1207378 (Part 2) - Use Downscaler to remove first-frame padding when downscaling GIFs. r=tn (5e995bdca2) - Bug 1207378 - Add FrameRect to non-skia version of BeginFrame. r=seth (939f0510eb) - Bug 1204394 (part 1) - Using StreamingLexer in the BMP decoder. r=seth. (77eaa1e125) - Bug 1204394 (part 2) - Add bmpsuite to the BMP reftests. r=seth. (237a902461) - Bug 1214476 - Remove unused code for encoding BMPv2 files. r=seth. (b8382357d7) - Bug 1213613 (part 1) - Formatting cleanups for nsBMPEncoder.h. r=seth. (4ee65bc173) - Bug 1213613 (part 2) - Move some BMP-related structs. r=seth. (15289784ce) - Bug 1215156 - move SetPixel* functions into nsBMPDecoder.cpp; r=seth (a143c843d4) - Bug 1215763 - part 1 - remove unnecessary nsAutoPtr.h includes; r=seth (f8a3a1f6b0) - Bug 1180715 (1/4) - Track image LoadTime to compare with file mtime. review=seth (bdbd25752c) - Bug 1180715 (2/4) - Provide a nsIFileURL interface for thumbnails. review=ttaubert (24c506569d) - Bug 1147562 - Update remaining callsites of newChannel before landing the shim in toolkit/ (r=gijs) (f7f2ab798f) - Bug 1163866 - Set originalURI correctly for moz-page-thumb:// channels. r=adw (3c7c97928e) - Bug 1180715 (3/4) - Drop parseURI from newChannel2. review=ttaubert (5b409e7d4b) - Bug 1180715 (4/4) - Use nsIURL methods instead of RegExp. review=ttaubert (1f7a026e06) - Bug 1209705 - Propagate the DrawResult for temporary surfaces to the caller in ClippedImage. r=tn (c5be1c7df4) - Bug 1215763 - part 2 - s/nsAutoPtr/UniquePtr/ in image/; r=seth (58c5da3bd2) - Bug 1215763 - part 3 - s/nsAutoArrayPtr/UniquePtr/ in nsBMPEncoder; r=seth (62bc939286) - Bug 1213613 (part 3) - Fix color-scaling of 16bpp BMP images. r=seth. (d06d511564) - Bug 1214072 (part 1) - Read BMP bitfields during metadata decoding. r=seth. (56cc2c02cd) - Bug 1214072 (part 2) - Implement transparency properly for BMP images. r=seth. (b6b07c2d90) - Bug 1218823 - use UniquePtr<> in preference to delete[] in image/; r=seth (5cd21f89a2) - Bug 1215334 (part 1) - Avoid creating a fake header for BMP files in ICO files. r=seth. (67506fc53e) - Bug 1215334 (part 2) - Avoid creating a fake header for BMP files in ICO files. r=seth. (cf6c3f4553) - spacing (b91c58ec87) - Bug 1196494 - part 1: remove unnecessary GetClientBounds call in CompositorParent. r=jrmuziel (685f49ae9c) - Bug 1180008 - Define gtk_window_get_window_type in mozgtk. r=karlt (e7ca7db4d3) - Bug 863512 - Fixing xul dnd panels for linux. r=enndeakin (5d0faaf0dc) - Bug 1195002 - draw to MozContainer window to allow GTK to draw decorations, r=karlt (7a84272fe9) - Bug 1210249 - don't mess with toplevel widget when client side decorations are enabled. r=karlt (464e9ce3fb) - bug 1180008 use mGdkWindow instead of finding it from gtk_widget_get_window(mShell) r=acomminos (024f8bd5a7) - bug 1180008 don't measure size of decorations for override-redirect windows r=acomminos (416ffda363) - Bug 1026803 part 1 - Factor out a common utility class for converting wrapping native times to TimeStamps; r=karlt (915ecdfc40) - Bug 1026803 part 2 - Add an assertion that SystemTimeConverter's template parameter is unsigned; r=karlt (68ccf1569d) - Bug 1026803 part 3 - Make some simplifications to SystemTimeConverter; r=karlt (c38719b7de) - Bug 1026803 part 4 - Convert GTK native event times to timestamps; r=karlt (74ef903992) - Bug 1026803 part 5 - Convert CurrentXXXTimeGetter classes from functors to helper classes; r=karlt (06d49e5965) - Bug 1026803 part 6 - Make SystemTimeConverter detect clock skew; r=karlt (d663ab72b2) - Bug 1026803 part 7 - Store the CurrentX11TimeGetter as a member object on nsWindow; r=karlt (f6a04e6e9c) - Bug 1026803 part 8 - Add ability for CurrentX11TimeGetter to perform an asynchronous request of the current time; r=karlt (961dab1d91) - Bug 1026803 part 8b - Factor out an IsTimeNewerThanTimeStamp method; r=karlt (1afb9f511a) - Bug 1026803 part 9 - Return DOMHighResTimeStamp objects from Event.timeStamp on Linux for Nightly/Aurora builds; r=karlt (975432dbd8) - Fix bustage from changeset 5c5dc6f367ac (bug 1026803) r=bustage on CLOSED TREE (c0be358f84) - fix bustage (e5e97018b6) - Bug 1196494 - part 2: only update nsWindow client offset when _NET_FRAME_EXTENTS property actually changes. r=eihrul (dd7e913d49) - Bug 1170342 - Disable XInput2 by default on GTK3. r=karlt (6a52d65a98) - Bug 1208904 - Fix a condition in nsWindow::SetNonClientMargins; r=roc (fe8c7b3e77) - Bug 1199892 - "Mouse cursor flickers in Flash object with wmode opaque/transparent". r=roc (6d50037e14) - Bug 1171101 - Remove pointer events and gesture scrolling dependencies. r=smaug (3aa5c89ea4) - Bug 978679. Implement touch events for GTK3. r=karlt (adafe58640) - Bug 1209774 - Transform from GDK coords to layout device pixels before calling DispatchEvent. r=karlt (4c81cf74ea) - Bug 1191293. Remove harmless assertion that is triggered by GTK3. r=masayuki (e0b9eb2c80) - Bug 978679. Convert GDK touch event coordinates properly. r=karlt (dfeae08f47) - restore some XP vs Vista differences, Bug 925599 bits (88ff57f06f) - Bug 1220392 - use UniquePtr<T[]> instead of nsAutoArrayPtr<T> in widget/; r=roc (35026aa4c9) - Bug 1214616 - Remove encoding conversion methods from nsPrimitiveHelpers. r=emk. (3a4bdbbc8e) - Bug 1216611 - add mozilla::MakeUniqueFallible and convert uses throughout the tree; r=Waldo (8672c2e3d9) - Bug 1216964 - remove nsAutoArrayPtr use from ActorsParent; r=khuey (ddff59241a) - Bug 1219903 - use UniquePtr<T[]> instead of delete[] calls in layout/generic/; r=dholbert (8b96067a6e) - Bug 1220190 - use UniquePtr<T[]> instead of delete[] calls in layout/xul/; r=dholbert (065b3c521c) - Bug 1220714 - use UniquePtr<T[]> instead of nsAutoArrayPtr<T> in layout/; r=dholbert (6a8245751c) - Bug 1221550 - use UniquePtr<T[]> instead of nsAutoArrayPtr<T> in intl/; r=smontagu (67868889e2) - Bug 1232374 - remove nsAutoArrayPtr usages from toolkit/; r=froydnj (d65586df26) - Bug 1170522 - expose whether or not we're in tablet mode to xul/js/css, r=jimm,ted (5cb134a8d4) - bug 1163872 - Fix a unified build issue in nsXPLookAndFeel.cpp. r=jimm (925e38abed) - bug 1217602 - remove nsIPKIParamBlock r=Cykesiopka (b5f0fc8dfd) - missing bit of old bug (fdcb4fe143) - Bug 1221453 - Use ObjDirPaths for GENERATED_INCLUDES and merge with LOCAL_INCLUDES. r=gps (a0537fff83) - Bug 1194948 - Build gfx/ipc in unified mode and mark as FAIL_ON_WARNINGS. r=BenWa (c88b356ac8) - Bug 815952 - Stop clearing clipboard data originating from a private window after closing private windows. r=ehsan (b94fea061d) - Bug 943296 - widget/gtk/nsDragService.cpp should assume Gtk uses UTF-8. r=karlt. (3613e87354) - Bug 1186661 - Draw drag and drop alpha pixmap correctly on GTK3. r=karlt (ca884af03b) - Bug 983843 - Switch to GtkOffscreenWindow for drag source widget, fixing ghost tabs on some GTK versions. r=karlt (ab56f9d764) - bug 1216916 clean up when InvokeDragSession() fails r=roc (30c811c33e) - Bug 1198128 - Fix -Wshadow warnings in widget/gtk. r=karlt (06bc60349e) - Merge branch 'dev' of https://github.com/rmottola/Arctic-Fox into dev (c79378b7c9) - Bug 1206915 - Move dumping of compositor textures under its own environment variable. r=mattwoodrow (b8ba4f0fbf) - Bug 1213007 - Part 1. Implementing gfxCrash. r=dvander (e307f9d543) - Bug 1208661 - Show display list and layer textures in-line in the HTML paint dump. r=BenWa (7047c68964) - Relax the driver crash guard on nightly and e10s builds. (bug 1200825, r=jgilbert) (1dd81b1257) - Bug 1214802 - gfxEnv - consolidate environment variables used by the graphics code in one place. r=botond (afb61356c6) - Bug 1217192 - Use gfxCriticalNote where we're already using the non-default construction parameter. r=mchang (d04ca17de5) - addback some crash stuff (8a78973a71) - Fix layers.acceleration.force-enabled not working. (bug 1212659, r=jrmuizel) (5eb85d8f64) - Bug 1142516 - Improve assertions and logging on the compositor side. r=Bas (cca63735e5) - Bug 1194335. Use a StackArray for RECTS so we see them in the minidumps. r=mattwoodrow (1a83a134e8) - Bug 1192058 - For DXGI_PRESENT_PARAMETERS, set pDirtyRects to nullptr if DirtyRectsCount == 0. r=BenWa (f78ff0df24) - Bug 1204922 - More information about crashes. r=bas (53cbd02c12) - Bug 1222033 - Rename gfxCrash to gfxDevCrash. r=jrmuizel (32351d0bc7) - Bug 1209812 (part 1) - Remove casts between cairo_format_t and gfxImageFormat. r=nical. (c1bc5cd74c) - Bug 1209812 (part 2) - Remove gfxImageFormat::A1. r=nical. (99f665ad80) - Bug 1182426 - Sort includes in VP8TrackEncoder.cpp alphabetically. r=roc (5f10334ba8) - Bug 1182426 - Don't try to encode new frames of a size other than the initial in VP8TrackEncoder. r=roc (8fb0b8f0d9) - Bug 1182426 - Flatten YUV formats conversion code in VP8TrackEncoder. r=roc (0853d098f7) - Bug 1182426 - Convert non-PlanarYCbCRImages in VP8TrackEncoder. r=roc (d2d78fa94a) - Bug 1204106 - Use correct alpha blend modes for OVER in CompositorOGL. r=jrmuizel (5cc211b9d6) - Bug 1207326 - Part 1: Correct projection clipping rectangle,r=matt.woodrow (8329afb6a7) - Bug 1207326 - Part 2: Add reftest,r=jmuizelaar (d17d6c5d4f) - Bug 1209446 - Make sure mFrameInProgress flag is set to true only when we actually begin drawing new frame. r=nical (4ff48c4149) - Disable screen and multiply mix-blend-mode support in the D3D11 and OGL compositors. (bug 1135271, r=mattwoodrow) (9b4c11a289) - Bug 1210189 - Use nsScreenGonk in nsWindow::StartRemoteDrawing() r=mwu (2653a33972) - Bug 1210514 - Fix color inversion when BasicCompositor is used on gonk r=nical (141fee3bfb) - Bug 1209812 (part 3) - Rename SurfaceFormat::R5G6B5 as R5G6B5_UINT16. r=Bas. (915e7eaba3) - Bug 1209812 (part 4) - Add comments to SurfaceFormat. r=jrmuizel,Bas. (ef1977582f) - Bug 1209812 (part 5) - Add endian-neutral variants to SurfaceFormat. r=nical,Bas. (93c49df8c5) - Bug 1171671 - Simplify Boot Animation control r=mwu (94c4f89b45) - Bug 1210182 - Implement GrallocTextureHostBasic r=nical (4e5ea5b92c) - Bug 1209812 (follow-up) - Android bustage fix on a CLOSED TREE. r=me. (8b2fa6268d) - Bug 1178513 - Added RGBA8888 to RGB565 converter. r=mattwoodrow (8ba5dbd3c9) - Bug 1160689 - thumbnail image corruption on certain videos. r=sotaro (278a2e29f6) - Bug 1204922: When ResizeBuffers fails, make no attempt to do subsequent paints until it succeeds again. r=milan (0c040d8228) - Bug 1215027 - Fix EndFrameForExternalComposition() r=nical (bab4690e54) - Bug 1215364 - Implement BasicCOmpositor::EndFrameForExternalComposition r=nical (c8b9c7bfb9) - Bug 1213968 - Renew the surface on iOS when resuming the compositor r=kats (73489dc21c) - Bug 1201318 - Factor out AddFamily. r=jdaggett (3f2556b8b4) - Bug 1201318 - revise OSX system font handling. r=mstange (f8a8f5f562) - Bug 1163877 - Part 1: Add storage for other FontFaceSets a FontFace is in. r=jdaggett (ab3a16b597) - Bug 1163877 - Part 2: Allow FontFaces to be added to multiple FontFaceSets. r=jdaggett (9b2dd7e5c9) - Bug 1163877 - Part 3: Update state on, and reflow documents for, all FontFaceSets that contain a FontFace whose user font entry updated. r=jdaggett (215db30569) - Bug 1192986 Fix test_interfaces.html to expect Cache API and font loading to be released. r=ehsan (8db9ef1df8) - Bug 1193019 - Rename CSSFontFaceLoadEvent to FontFaceSetLoadEvent. r=khuey (53f373c53d) - Bug 1163877 - Part 4: Tests. r=jdaggett (c6053ca8b4) - Bug 1180415 - initialize downloadable font pattern from FTFace. r=karlt (04aa59ba79) - Bug 1163491 - map local fontnames to fontconfig patterns. r=karlt (ad10ebde2a) - Bug 543715 p1 - distinguish between italic and oblique. r=jfkthame (4c9a0abf64) - Bug 543715 p2 - italic/oblique reftests. r=jfkthame (90fb927148) - bug 1178733 - enable APZ for iOS. r=kats (e41702d9cd) - Bug 1169956. Backout bug 1073209 for tiled image regressions on OS X. r=jrmuizel (a80a29aaa3) - Bug 1073209 - Eliminate usage of CreateSamplingRestrictedDrawable on d2d backends. r=jrmuizel (9ac8781a52) - Bug 1204136 - Align DisplayPort on non-tiling platform. r=botond (1d8be17663) - Bug 1122918 - Put the logical values for 'float' and 'clear' behind a pref, and enable them only on nightly builds and for B2G. r=heycam (a428b34d66) - Bug 1183484 - Cycle collect FontFaceSetIterator. r=bzbarsky (dc49f3f098) - Bug 1027579 - Do not load fonts from the cache if LOAD_BYPASS_CACHE is set. r=jfkthame r=bz (6cf20c9119) - Bug 1201318 - Use nsAutoReleasePool from nsCocoaUtils.h. r=jdaggett (bfe5f05086) - Bug 1201403 - streamline MacOSFontEntry::HasFontTable implementation. r=jfkthame (da06064b86) - Bug 1165611 - fix font smoothing under Linux. r=karlt (166a96603b) - Bug 1160506 - support intra-family font fallback. r=heycam (f56f0507d0) - Bug 1165693 - Cache the result of calling FcConfigSubstitute for our sentinel font name, to make gfxFcPlatformFontList::FindFamily less expensive. r=jdaggett (09ace74bc1) - Bug 1165693 - patch 2 - Cache family-name lookups in gfxFcPlatformFontList::FindFamily, to avoid repeating expensive calls to FcConfigSubstitute. r=jdaggett (ab34bf45c0) - Bug 1174946 - Back out the (incorrectly-implemented) caching of sentinelFirstFamily from bug 1165693, which should be largely overshadowed by the mFcSubstituteCache anyway. r=jdaggett (9b7784b7a6) - Bug 1165766 - Crash in AddFontSetFamilies() r=jtd (13cba8e6c7) - Bug 1170421 - return first font suggested by fontconfig as the default font. r=karlt (426c6bd348) - Bug 1008169 - Font selection and font size dropdowns are reacting very slowly on press up/down,r=jaws (8e395026e1) - Bug 1166161 - Display available font from font.name-list.{family}.{lang} as fallback default font, instead of empty string. (045855761a) - Bug 1187680 - Use NSVisualEffectMaterialMenu for menus if it's available. r=smichaud (0cede2f295) - Bug 1190257. Use the previous vsync timestamp on windows 10. r=jrmuizel (7cf7e2644f) - Bug 1220699 - Add telemetry probe to measure vsync latency in the parent refresh driver. r=avih (347936dffb) - Bug 1221674 - Add telemetry probe in the content process to measure the time between refresh driver ticks. r=kats (1d1b885f10) - Bug 1198362 - Delete PreciseRefreshDriverTimer. r=roc (8fdcca758f) - Bug 1197898 - Delete vsync refresh driver preference. r=kats (eab85ba8dd) - Bug 1210250. Fallback to GDI fonts with a skia backend. r=jwatt (8c374b4bd3) - Bug 1208927 - Initialize queryD3DKMTStatistics so that it can't be accessed uninitialized; r=jrmuizel (69c6781a82) - Bug 1144946 - Delete PreciseRefreshDriverTimerWindowsDwmVsync refresh driver timer. r=roc (a78ccb3d42) - Bug 1187784 (part 4) - Replace nsBaseHashtable::EnumerateRead() calls in layout/ with iterators. r=heycam. (6a09016e6d) - Bug 1187784 (part 3) - Replace nsBaseHashtable::EnumerateRead() calls in layout/ with iterators. r=heycam. (1ce8abd863) - Bug 1187784 (part 2) - Replace nsBaseHashtable::EnumerateRead() calls in layout/ with iterators. r=heycam. (cd98be2f19) - Bug 1187784 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in layout/ with iterators. r=heycam. (09022514a9) - Bug 1217230 - Set mNeedToRecomputeVisibility true only when style or layout flush. r=seth (8f3edd57c0) - Bug 1167281 - sort scalable fonts first when resolving generic families under Linux. r=karlt (e2ecb89f31) - Bug 1186875 - check if FcFontSort returns non-null. r=jtd (087905da51) - Bug 1218617 - Invalidate whole widget area after external composition r=mattwoodrow (3901f416f5) - Bug 1153499 - Enable push and sw prefs. r=dougt,ehsan (ca0f3a105d) - Bug 1141415 - add expire setting of permission to SpecialPowers. r=jmaher (372bc0c930) - partial Bug 1196665 - Add originAttributes into SpecialPowers. r=bholley (b8407a1bcc) - Bug 1213151 - Part 1: Add a SpecialPowers API for cleaning up the STS data that works in both e10s and non-e10s modes; r=jdm (04bba17fd3) - Bug 1213151 - Part 2: Use SpecialPowers.cleanUpSTSData() in a few tests; r=jdm (cce5f23dac) - Bug 1214593 - Remove service worker periodic updater. r=ehsan (87cfebcd0d) - fix tests, fix spaces (9cba57e7ff) - Bug 1207499 - Part 10: Remove use of expression closure from testing/. r=jmaher (652faa8963) - Bug 1204154 - Clean up jar manifests that needlessly specify the source file. r=dao (f116e33bed) - Bug 1222943 (part 1) - Change Touch::mRadius from nsIntPoint to LayoutDeviceIntPoint. r=kats. (dcc6c15797) - Bug 1222943 (part 2) - Remove an unnecessary call to ToUnknownSize(). r=botond. (08f644f194) - Bug 1220925 - Event::GetScreenCoords should return CSSIntPoint instead of LayoutDevicePoint. r=botond (4e4f54e8e7) - Bug 943294 - Leave dealing with legacy codepages for clipboard data to Windows itself. r=jmathies. (124ecbfa3e) - Bug 1243507 - Reimplement non-Unicode clipboard formats as bug 943294 broke drag and drop between some Unicode-unaware apps. r=jimm (02edc31ed9) - ported Bug 1254980 - Ensure that text/html is still written to the clipboard. r=enndeakin a=sylvestre (9cd4c0e41a) - nsWindow: build fix (1c3e798a89)
2989 lines
103 KiB
C++
2989 lines
103 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/DecodedAudioDataSink.h"
|
|
#include "mediasink/AudioSinkWrapper.h"
|
|
#include "mediasink/VideoSink.h"
|
|
#include "mediasink/DecodedStream.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"
|
|
|
|
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, mDecoder.get(), ##__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, mDecoder.get(), ##__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.
|
|
const int64_t AMPLE_AUDIO_USECS = 1000000;
|
|
|
|
} // namespace detail
|
|
|
|
// When we're only playing audio and we don't have a video stream, we divide
|
|
// AMPLE_AUDIO_USECS and LOW_AUDIO_USECS by the following value. This reduces
|
|
// the amount of decoded audio we buffer, reducing our memory usage. We only
|
|
// need to decode far ahead when we're decoding video using software decoding,
|
|
// as otherwise a long video decode could cause an audio underrun.
|
|
const int64_t NO_VIDEO_AMPLE_AUDIO_DIVISOR = 8;
|
|
|
|
// 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;
|
|
|
|
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;
|
|
static const uint32_t VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE = 9999;
|
|
|
|
static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
|
|
static uint32_t sVideoQueueHWAccelSize = MIN_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", MIN_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) :
|
|
mDecoder(aDecoder),
|
|
mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
|
|
/* aSupportsTailDispatch = */ true)),
|
|
mWatchManager(this, mTaskQueue),
|
|
mRealTime(aRealTime),
|
|
mDispatchedStateMachine(false),
|
|
mDelayedScheduler(mTaskQueue),
|
|
mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"),
|
|
mCurrentFrameID(0),
|
|
mObservedDuration(TimeUnit(), "MediaDecoderStateMachine::mObservedDuration"),
|
|
mFragmentEndTime(-1),
|
|
mReader(aReader),
|
|
mDecodedAudioEndTime(-1),
|
|
mDecodedVideoEndTime(-1),
|
|
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, "MediaDecoderStateMachine::mAudioCaptured"),
|
|
mAudioCompleted(false, "MediaDecoderStateMachine::mAudioCompleted"),
|
|
mNotifyMetadataBeforeFirstFrame(false),
|
|
mDispatchedEventToDecode(false),
|
|
mQuickBuffering(false),
|
|
mMinimizePreroll(false),
|
|
mDecodeThreadWaiting(false),
|
|
mDropAudioUntilNextDiscontinuity(false),
|
|
mDropVideoUntilNextDiscontinuity(false),
|
|
mDecodeToSeekTarget(false),
|
|
mCurrentTimeBeforeSeek(0),
|
|
mCorruptFrames(60),
|
|
mDecodingFirstFrame(true),
|
|
mSentLoadedMetadataEvent(false),
|
|
mSentFirstFrameLoadedEvent(false, "MediaDecoderStateMachine::mSentFirstFrameLoadedEvent"),
|
|
mSentPlaybackEndedEvent(false),
|
|
mStreamSink(new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue)),
|
|
mResource(aDecoder->GetResource()),
|
|
mAudioOffloading(false),
|
|
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)"),
|
|
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)")
|
|
{
|
|
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_NewRunnableMethod(this, &MediaDecoderStateMachine::InitializationTask);
|
|
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);
|
|
}
|
|
|
|
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()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
// Connect mirrors.
|
|
mBuffered.Connect(mReader->CanonicalBuffered());
|
|
mEstimatedDuration.Connect(mDecoder->CanonicalEstimatedDuration());
|
|
mExplicitDuration.Connect(mDecoder->CanonicalExplicitDuration());
|
|
mPlayState.Connect(mDecoder->CanonicalPlayState());
|
|
mNextPlayState.Connect(mDecoder->CanonicalNextPlayState());
|
|
mLogicallySeeking.Connect(mDecoder->CanonicalLogicallySeeking());
|
|
mVolume.Connect(mDecoder->CanonicalVolume());
|
|
mLogicalPlaybackRate.Connect(mDecoder->CanonicalPlaybackRate());
|
|
mPreservesPitch.Connect(mDecoder->CanonicalPreservesPitch());
|
|
mSameOriginMedia.Connect(mDecoder->CanonicalSameOriginMedia());
|
|
mPlaybackBytesPerSecond.Connect(mDecoder->CanonicalPlaybackBytesPerSecond());
|
|
mPlaybackRateReliable.Connect(mDecoder->CanonicalPlaybackRateReliable());
|
|
mDecoderPosition.Connect(mDecoder->CanonicalDecoderPosition());
|
|
mMediaSeekable.Connect(mDecoder->CanonicalMediaSeekable());
|
|
|
|
// Initialize watchers.
|
|
mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
|
|
mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
|
|
mWatchManager.Watch(mAudioCompleted, &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);
|
|
mWatchManager.Watch(mSameOriginMedia, &MediaDecoderStateMachine::SameOriginMediaChanged);
|
|
mWatchManager.Watch(mSentFirstFrameLoadedEvent, &MediaDecoderStateMachine::AdjustAudioThresholds);
|
|
mWatchManager.Watch(mAudioCaptured, &MediaDecoderStateMachine::AdjustAudioThresholds);
|
|
|
|
// Propagate mSameOriginMedia to mDecodedStream.
|
|
SameOriginMediaChanged();
|
|
}
|
|
|
|
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->mDecoder->GetAudioChannel());
|
|
};
|
|
return new AudioSinkWrapper(mTaskQueue, audioSinkCreator);
|
|
}
|
|
|
|
already_AddRefed<media::MediaSink>
|
|
MediaDecoderStateMachine::CreateMediaSink(bool aAudioCaptured)
|
|
{
|
|
// TODO: We can't really create a new DecodedStream until OutputStreamManager
|
|
// is extracted. It is tricky that the implementation of DecodedStream
|
|
// happens to allow reuse after shutdown without creating a new one.
|
|
RefPtr<media::MediaSink> audioSink = aAudioCaptured ?
|
|
mStreamSink : CreateAudioSink();
|
|
|
|
RefPtr<media::MediaSink> mediaSink =
|
|
new VideoSink(mTaskQueue, audioSink, mVideoQueue,
|
|
mDecoder->GetVideoFrameContainer(), mRealTime,
|
|
mDecoder->GetFrameStatistics(), AUDIO_DURATION_USECS,
|
|
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 == -1| means no decoded audio at all so the
|
|
// returned duration is 0.
|
|
return mDecodedAudioEndTime != -1 ? 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 decToTar=%d minPrl=%d seek=%d enufVid=%d",
|
|
IsVideoDecoding(), mDecodeToSeekTarget, mMinimizePreroll,
|
|
mState == DECODER_STATE_SEEKING,
|
|
HaveEnoughDecodedVideo());
|
|
return IsVideoDecoding() &&
|
|
((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) ||
|
|
(IsDecodingFirstFrame() &&
|
|
IsVideoDecoding() && 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 decToTar=%d minPrl=%d seek=%d enufAud=%d",
|
|
IsAudioDecoding(), mDecodeToSeekTarget, mMinimizePreroll,
|
|
mState == DECODER_STATE_SEEKING,
|
|
HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate));
|
|
|
|
return IsAudioDecoding() &&
|
|
((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) ||
|
|
(IsDecodingFirstFrame() &&
|
|
IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
|
|
(!mMinimizePreroll &&
|
|
!HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate) &&
|
|
(mState != DECODER_STATE_SEEKING || mDecodeToSeekTarget)));
|
|
}
|
|
|
|
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());
|
|
mDecodedAudioEndTime = audio->GetEndTime();
|
|
|
|
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.mType == SeekTarget::PrevSyncPoint &&
|
|
mCurrentSeek.mTarget.mTime > 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.mType = SeekTarget::Accurate;
|
|
}
|
|
if (mCurrentSeek.mTarget.mType == SeekTarget::PrevSyncPoint) {
|
|
// 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::OnAudioPopped(const RefPtr<MediaData>& aSample)
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
mPlaybackOffset = std::max(mPlaybackOffset.Ref(), aSample->mOffset);
|
|
UpdateNextFrameStatus();
|
|
DispatchAudioDecodeTaskIfNeeded();
|
|
MaybeStartBuffering();
|
|
}
|
|
|
|
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();
|
|
}));
|
|
|
|
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());
|
|
mDecodedVideoEndTime = video ? video->GetEndTime() : mDecodedVideoEndTime;
|
|
|
|
SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
|
|
(video ? video->mTime : -1),
|
|
(video ? video->GetEndTime() : -1),
|
|
(video ? video->mDiscontinuity : 0));
|
|
|
|
// Check frame validity here for every decoded frame in order to have a
|
|
// better chance to make the decision of turning off HW acceleration.
|
|
CheckFrameValidity(aVideoSample->As<VideoData>());
|
|
|
|
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();
|
|
}
|
|
|
|
// Schedule the state machine to send stream data as soon as possible if
|
|
// the VideoQueue() is empty or contains one frame before the Push().
|
|
//
|
|
// The state machine threads requires a frame in VideoQueue() that is `in
|
|
// the future` to gather precise timing information. The head of
|
|
// VideoQueue() is always `in the past`.
|
|
//
|
|
// Schedule the state machine as soon as possible to render the video
|
|
// frame or delay the state machine thread accurately.
|
|
if (VideoQueue().GetSize() <= 2) {
|
|
ScheduleStateMachine();
|
|
}
|
|
|
|
// 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.mType == SeekTarget::PrevSyncPoint &&
|
|
mCurrentSeek.mTarget.mTime > 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.mType = SeekTarget::Accurate;
|
|
}
|
|
if (mCurrentSeek.mTarget.mType == SeekTarget::PrevSyncPoint) {
|
|
// Non-precise seek; 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) {
|
|
mDecodeToSeekTarget = false;
|
|
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);
|
|
ScheduleStateMachineCrossThread();
|
|
return NS_OK;
|
|
}
|
|
|
|
void MediaDecoderStateMachine::StopPlayback()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
DECODER_LOG("StopPlayback()");
|
|
|
|
mDecoder->DispatchPlaybackStopped();
|
|
|
|
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");
|
|
mDecoder->DispatchPlaybackStarted();
|
|
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[] = {
|
|
"NONE",
|
|
"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)) {
|
|
ScheduleStateMachine();
|
|
mDecodingFirstFrame = true;
|
|
SetState(DECODER_STATE_DECODING_NONE);
|
|
}
|
|
}
|
|
|
|
void 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);
|
|
|
|
mQueuedSeek.RejectIfExists(__func__);
|
|
mPendingSeek.RejectIfExists(__func__);
|
|
mCurrentSeek.RejectIfExists(__func__);
|
|
|
|
if (IsPlaying()) {
|
|
StopPlayback();
|
|
}
|
|
|
|
Reset();
|
|
|
|
mMediaSink->Shutdown();
|
|
|
|
// Shut down our start time rendezvous.
|
|
if (mStartTimeRendezvous) {
|
|
mStartTimeRendezvous->Destroy();
|
|
}
|
|
|
|
// Put a task in the decode queue to shutdown the reader.
|
|
// the queue to spin down.
|
|
InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::Shutdown)
|
|
->Then(OwnerThread(), __func__, this,
|
|
&MediaDecoderStateMachine::FinishShutdown,
|
|
&MediaDecoderStateMachine::FinishShutdown);
|
|
DECODER_LOG("Shutdown started");
|
|
}
|
|
|
|
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::SameOriginMediaChanged()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
mStreamSink->SetSameOrigin(mSameOriginMedia);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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__);
|
|
}
|
|
|
|
NS_ASSERTION(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.mTime);
|
|
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.mTime;
|
|
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.mTime = 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.mTime);
|
|
|
|
nsCOMPtr<nsIRunnable> startEvent =
|
|
NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
|
|
mDecoder,
|
|
&MediaDecoder::SeekingStarted,
|
|
mCurrentSeek.mTarget.mEventVisibility);
|
|
AbstractThread::MainThread()->Dispatch(startEvent.forget());
|
|
|
|
// Reset our state machine and decoding pipeline before seeking.
|
|
Reset();
|
|
|
|
// Do the seek.
|
|
RefPtr<MediaDecoderStateMachine> self = this;
|
|
mSeekRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
|
|
&MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime,
|
|
Duration().ToMicroseconds())
|
|
->Then(OwnerThread(), __func__,
|
|
[self] (int64_t) -> 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->mDecodeToSeekTarget = true;
|
|
self->DispatchDecodeTasksIfNeeded();
|
|
}, [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 ? 0 : GetMediaTime();
|
|
|
|
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()) {
|
|
// mDecodedAudioEndTime could be -1 when no audio samples are decoded.
|
|
// But that is fine since we consider ourself as low in decoded data when
|
|
// we don't have any decoded audio samples at all.
|
|
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.
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
|
|
AbstractThread::MainThread()->Dispatch(event.forget());
|
|
}
|
|
|
|
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);
|
|
mDecoder->DispatchSetMediaSeekable(mReader->IsMediaSeekable());
|
|
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 =
|
|
false;
|
|
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());
|
|
nsAutoPtr<MediaInfo> info(new MediaInfo());
|
|
*info = mInfo;
|
|
MediaDecoderEventVisibility visibility = mSentLoadedMetadataEvent?
|
|
MediaDecoderEventVisibility::Suppressed :
|
|
MediaDecoderEventVisibility::Observable;
|
|
nsCOMPtr<nsIRunnable> metadataLoadedEvent =
|
|
new MetadataEventRunner(mDecoder, info, mMetadataTags, visibility);
|
|
AbstractThread::MainThread()->Dispatch(metadataLoadedEvent.forget());
|
|
mSentLoadedMetadataEvent = true;
|
|
}
|
|
|
|
void
|
|
MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
nsAutoPtr<MediaInfo> info(new MediaInfo());
|
|
*info = mInfo;
|
|
MediaDecoderEventVisibility visibility = mSentFirstFrameLoadedEvent?
|
|
MediaDecoderEventVisibility::Suppressed :
|
|
MediaDecoderEventVisibility::Observable;
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new FirstFrameLoadedEventRunner(mDecoder, info, visibility);
|
|
AbstractThread::MainThread()->Dispatch(event.forget());
|
|
mSentFirstFrameLoadedEvent = true;
|
|
}
|
|
|
|
bool
|
|
MediaDecoderStateMachine::IsDecodingFirstFrame()
|
|
{
|
|
return mState == DECODER_STATE_DECODING && mDecodingFirstFrame;
|
|
}
|
|
|
|
void
|
|
MediaDecoderStateMachine::AdjustAudioThresholds()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
// Experiments show that we need to buffer more if audio is captured to avoid
|
|
// audio glitch. See bug 1188643 comment 16 for the details.
|
|
int64_t divisor = mAudioCaptured ? NO_VIDEO_AMPLE_AUDIO_DIVISOR / 2
|
|
: NO_VIDEO_AMPLE_AUDIO_DIVISOR;
|
|
|
|
// We're playing audio only. We don't need to worry about slow video
|
|
// decodes causing audio underruns, so don't buffer so much audio in
|
|
// order to reduce memory usage.
|
|
if (HasAudio() && !HasVideo() && mSentFirstFrameLoadedEvent) {
|
|
mAmpleAudioThresholdUsecs = detail::AMPLE_AUDIO_USECS / divisor;
|
|
mLowAudioThresholdUsecs = detail::LOW_AUDIO_USECS / divisor;
|
|
mQuickBufferingLowDataThresholdUsecs =
|
|
detail::QUICK_BUFFERING_LOW_DATA_USECS / divisor;
|
|
|
|
// Check if we need to stop audio prerolling for thresholds changed.
|
|
if (mIsAudioPrerolling && DonePrerollingAudio()) {
|
|
StopPrerollingAudio();
|
|
}
|
|
}
|
|
}
|
|
|
|
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.mTime;
|
|
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 videoStart = video ? video->mTime : seekTime;
|
|
int64_t audioStart = audio ? audio->mTime : seekTime;
|
|
newCurrentTime = std::min(audioStart, videoStart);
|
|
} 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();
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate);
|
|
AbstractThread::MainThread()->Dispatch(event.forget());
|
|
}
|
|
}
|
|
|
|
class DecoderDisposer
|
|
{
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecoderDisposer)
|
|
DecoderDisposer(MediaDecoder* aDecoder, MediaDecoderStateMachine* aStateMachine)
|
|
: mDecoder(aDecoder), mStateMachine(aStateMachine) {}
|
|
|
|
void OnTaskQueueShutdown()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mStateMachine);
|
|
MOZ_ASSERT(mDecoder);
|
|
mStateMachine->BreakCycles();
|
|
mDecoder->BreakCycles();
|
|
mStateMachine = nullptr;
|
|
mDecoder = nullptr;
|
|
}
|
|
|
|
private:
|
|
virtual ~DecoderDisposer() {}
|
|
RefPtr<MediaDecoder> mDecoder;
|
|
RefPtr<MediaDecoderStateMachine> mStateMachine;
|
|
};
|
|
|
|
void
|
|
MediaDecoderStateMachine::DispatchShutdown()
|
|
{
|
|
mStreamSink->BeginShutdown();
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
|
|
OwnerThread()->Dispatch(runnable.forget());
|
|
}
|
|
|
|
void
|
|
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();
|
|
mPlaybackBytesPerSecond.DisconnectIfConnected();
|
|
mPlaybackRateReliable.DisconnectIfConnected();
|
|
mDecoderPosition.DisconnectIfConnected();
|
|
mMediaSeekable.DisconnectIfConnected();
|
|
|
|
mDuration.DisconnectAll();
|
|
mIsShutdown.DisconnectAll();
|
|
mNextFrameStatus.DisconnectAll();
|
|
mCurrentPosition.DisconnectAll();
|
|
mPlaybackOffset.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?");
|
|
// We must daisy-chain these events to destroy the decoder. We must
|
|
// destroy the decoder on the main thread, but we can't destroy the
|
|
// decoder while this thread holds the decoder monitor. We can't
|
|
// dispatch an event to the main thread to destroy the decoder from
|
|
// here, as the event may run before the dispatch returns, and we
|
|
// hold the decoder monitor here. We also want to guarantee that the
|
|
// state machine is destroyed on the main thread, and so the
|
|
// event runner running this function (which holds a reference to the
|
|
// state machine) needs to finish and be released in order to allow
|
|
// that. So we dispatch an event to run after this event runner has
|
|
// finished and released its monitor/references. That event then will
|
|
// dispatch an event to the main thread to release the decoder and
|
|
// state machine.
|
|
DECODER_LOG("Shutting down state machine task queue");
|
|
RefPtr<DecoderDisposer> disposer = new DecoderDisposer(mDecoder, this);
|
|
OwnerThread()->BeginShutdown()->Then(AbstractThread::MainThread(), __func__,
|
|
disposer.get(),
|
|
&DecoderDisposer::OnTaskQueueShutdown,
|
|
&DecoderDisposer::OnTaskQueueShutdown);
|
|
}
|
|
|
|
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:
|
|
return NS_OK;
|
|
|
|
case DECODER_STATE_DECODING_NONE: {
|
|
SetState(DECODER_STATE_DECODING_METADATA);
|
|
ScheduleStateMachine();
|
|
return NS_OK;
|
|
}
|
|
|
|
case DECODER_STATE_DECODING_METADATA: {
|
|
if (!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));
|
|
|
|
}
|
|
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 (VideoQueue().GetSize() > 1 ||
|
|
(HasAudio() && !mAudioCompleted) ||
|
|
(mAudioCaptured && !mStreamSink->IsFinished()))
|
|
{
|
|
// 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();
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
|
|
AbstractThread::MainThread()->Dispatch(event.forget());
|
|
|
|
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 ||
|
|
mState == DECODER_STATE_DECODING_NONE);
|
|
|
|
// 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 = -1;
|
|
mDecodedAudioEndTime = -1;
|
|
mAudioCompleted = false;
|
|
AudioQueue().Reset();
|
|
VideoQueue().Reset();
|
|
mFirstVideoFrameAfterSeek = nullptr;
|
|
mDropAudioUntilNextDiscontinuity = true;
|
|
mDropVideoUntilNextDiscontinuity = true;
|
|
mDecodeToSeekTarget = false;
|
|
|
|
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());
|
|
}
|
|
|
|
void
|
|
MediaDecoderStateMachine::CheckFrameValidity(VideoData* aData)
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
// Update corrupt-frames statistics
|
|
if (aData->mImage && !aData->mImage->IsValid()) {
|
|
FrameStatistics& frameStats = mDecoder->GetFrameStatistics();
|
|
frameStats.NotifyCorruptFrame();
|
|
// If more than 10% of the last 30 frames have been corrupted, then try disabling
|
|
// hardware acceleration. We use 10 as the corrupt value because RollingMean<>
|
|
// only supports integer types.
|
|
mCorruptFrames.insert(10);
|
|
if (mReader->VideoIsHardwareAccelerated() &&
|
|
frameStats.GetPresentedFrames() > 60 &&
|
|
mCorruptFrames.mean() >= 2 /* 20% */) {
|
|
nsCOMPtr<nsIRunnable> task =
|
|
NS_NewRunnableMethod(mReader, &MediaDecoderReader::DisableHardwareAcceleration);
|
|
DecodeTaskQueue()->Dispatch(task.forget());
|
|
mCorruptFrames.clear();
|
|
gfxCriticalNote << "Too many dropped/corrupted frames, disabling DXVA";
|
|
}
|
|
} else {
|
|
mCorruptFrames.insert(0);
|
|
}
|
|
}
|
|
|
|
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.mTime;
|
|
|
|
// 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.mType == SeekTarget::Accurate);
|
|
|
|
CheckedInt64 sampleDuration =
|
|
FramesToUsecs(audio->mFrames, mInfo.mAudio.mRate);
|
|
if (!sampleDuration.isValid()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (audio->mTime + sampleDuration.value() <= mCurrentSeek.mTarget.mTime) {
|
|
// 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.mTime) {
|
|
// 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.mTime >= audio->mTime,
|
|
"Target must at or be after data start.");
|
|
NS_ASSERTION(mCurrentSeek.mTarget.mTime < audio->mTime + sampleDuration.value(),
|
|
"Data must end after target.");
|
|
|
|
CheckedInt64 framesToPrune =
|
|
UsecsToFrames(mCurrentSeek.mTarget.mTime - 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.mTime,
|
|
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());
|
|
VERBOSE_LOG("[%s]", __func__);
|
|
|
|
mMediaSinkVideoPromise.Complete();
|
|
ScheduleStateMachine();
|
|
}
|
|
|
|
void
|
|
MediaDecoderStateMachine::OnMediaSinkVideoError()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
VERBOSE_LOG("[%s]", __func__);
|
|
|
|
mMediaSinkVideoPromise.Complete();
|
|
if (HasAudio()) {
|
|
return;
|
|
}
|
|
DecodeError();
|
|
}
|
|
|
|
void MediaDecoderStateMachine::OnMediaSinkAudioComplete()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
VERBOSE_LOG("[%s]", __func__);
|
|
|
|
mMediaSinkAudioPromise.Complete();
|
|
// Set true only when we have audio.
|
|
mAudioCompleted = mInfo.HasAudio();
|
|
// To notify PlaybackEnded as soon as possible.
|
|
ScheduleStateMachine();
|
|
}
|
|
|
|
void MediaDecoderStateMachine::OnMediaSinkAudioError()
|
|
{
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
VERBOSE_LOG("[%s]", __func__);
|
|
|
|
mMediaSinkAudioPromise.Complete();
|
|
// Set true only when we have audio.
|
|
mAudioCompleted = mInfo.HasAudio();
|
|
|
|
// 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();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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);
|
|
mStreamSink->AddOutput(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);
|
|
mStreamSink->RemoveOutput(aStream);
|
|
if (!mStreamSink->HasConsumers()) {
|
|
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
|