@oxyhq/bloom 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/docs/media-flight.mdx +42 -1
  2. package/lib/commonjs/media-flight/MediaFlightHost.js +14 -1
  3. package/lib/commonjs/media-flight/MediaFlightHost.js.map +1 -1
  4. package/lib/commonjs/media-flight/MediaFlightHost.web.js +9 -1
  5. package/lib/commonjs/media-flight/MediaFlightHost.web.js.map +1 -1
  6. package/lib/commonjs/media-flight/MediaFlightLayer.web.js +39 -0
  7. package/lib/commonjs/media-flight/MediaFlightLayer.web.js.map +1 -1
  8. package/lib/commonjs/media-flight/MediaSurface.js +25 -0
  9. package/lib/commonjs/media-flight/MediaSurface.js.map +1 -1
  10. package/lib/commonjs/media-flight/constants.js +23 -1
  11. package/lib/commonjs/media-flight/constants.js.map +1 -1
  12. package/lib/commonjs/media-flight/store.js +4 -1
  13. package/lib/commonjs/media-flight/store.js.map +1 -1
  14. package/lib/module/media-flight/MediaFlightHost.js +14 -1
  15. package/lib/module/media-flight/MediaFlightHost.js.map +1 -1
  16. package/lib/module/media-flight/MediaFlightHost.web.js +9 -1
  17. package/lib/module/media-flight/MediaFlightHost.web.js.map +1 -1
  18. package/lib/module/media-flight/MediaFlightLayer.web.js +39 -0
  19. package/lib/module/media-flight/MediaFlightLayer.web.js.map +1 -1
  20. package/lib/module/media-flight/MediaSurface.js +26 -1
  21. package/lib/module/media-flight/MediaSurface.js.map +1 -1
  22. package/lib/module/media-flight/constants.js +22 -0
  23. package/lib/module/media-flight/constants.js.map +1 -1
  24. package/lib/module/media-flight/store.js +4 -1
  25. package/lib/module/media-flight/store.js.map +1 -1
  26. package/lib/typescript/commonjs/media-flight/MediaFlightHost.d.ts +2 -1
  27. package/lib/typescript/commonjs/media-flight/MediaFlightHost.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/media-flight/MediaFlightHost.web.d.ts +9 -1
  29. package/lib/typescript/commonjs/media-flight/MediaFlightHost.web.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/media-flight/MediaFlightLayer.web.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/media-flight/MediaSurface.d.ts +4 -1
  32. package/lib/typescript/commonjs/media-flight/MediaSurface.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/media-flight/constants.d.ts +20 -0
  34. package/lib/typescript/commonjs/media-flight/constants.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/media-flight/index.d.ts +1 -1
  36. package/lib/typescript/commonjs/media-flight/index.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/media-flight/index.web.d.ts +1 -1
  38. package/lib/typescript/commonjs/media-flight/index.web.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/media-flight/store.d.ts +4 -1
  40. package/lib/typescript/commonjs/media-flight/store.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/media-flight/types.d.ts +24 -7
  42. package/lib/typescript/commonjs/media-flight/types.d.ts.map +1 -1
  43. package/lib/typescript/module/media-flight/MediaFlightHost.d.ts +2 -1
  44. package/lib/typescript/module/media-flight/MediaFlightHost.d.ts.map +1 -1
  45. package/lib/typescript/module/media-flight/MediaFlightHost.web.d.ts +9 -1
  46. package/lib/typescript/module/media-flight/MediaFlightHost.web.d.ts.map +1 -1
  47. package/lib/typescript/module/media-flight/MediaFlightLayer.web.d.ts.map +1 -1
  48. package/lib/typescript/module/media-flight/MediaSurface.d.ts +4 -1
  49. package/lib/typescript/module/media-flight/MediaSurface.d.ts.map +1 -1
  50. package/lib/typescript/module/media-flight/constants.d.ts +20 -0
  51. package/lib/typescript/module/media-flight/constants.d.ts.map +1 -1
  52. package/lib/typescript/module/media-flight/index.d.ts +1 -1
  53. package/lib/typescript/module/media-flight/index.d.ts.map +1 -1
  54. package/lib/typescript/module/media-flight/index.web.d.ts +1 -1
  55. package/lib/typescript/module/media-flight/index.web.d.ts.map +1 -1
  56. package/lib/typescript/module/media-flight/store.d.ts +4 -1
  57. package/lib/typescript/module/media-flight/store.d.ts.map +1 -1
  58. package/lib/typescript/module/media-flight/types.d.ts +24 -7
  59. package/lib/typescript/module/media-flight/types.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/media-flight/MediaFlightHost.tsx +21 -4
  62. package/src/media-flight/MediaFlightHost.web.tsx +20 -4
  63. package/src/media-flight/MediaFlightLayer.web.tsx +48 -0
  64. package/src/media-flight/MediaSurface.tsx +36 -2
  65. package/src/media-flight/constants.ts +22 -0
  66. package/src/media-flight/index.ts +3 -0
  67. package/src/media-flight/index.web.ts +3 -0
  68. package/src/media-flight/store.ts +4 -1
  69. package/src/media-flight/types.ts +27 -7
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,aAAa,EACb,SAAS,EACT,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,WAAW,EACX,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,aAAa,EACb,SAAS,EACT,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,WAAW,EACX,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -14,6 +14,6 @@ export { useMediaFlight } from './use-media-flight';
14
14
  export { DEFAULT_FLIGHT_CORNER_RADIUS, flightProgress, handOffFlight, hasFlight, releaseFlight, resetMediaFlight, } from './store';
15
15
  export { SURFACE_MOUNT_TIMEOUT_MS } from './constants';
16
16
  export { loadExpoVideo, warnExpoVideoUnavailable, provideExpoVideo, resetExpoVideoModule, type ExpoVideoLike, type VideoPlayerLike, type VideoSurfaceContentFit, type VideoSurfaceType, type VideoViewLikeProps, } from './expo-video-module';
17
- export type { MediaFlight, MediaFlightAnchorNode, MediaFlightController, MediaFlightHostProps, MediaFlightOptions, MediaSurfaceContent, MediaSurfaceImage, MediaSurfaceVideo, MeasuredRect, } from './types';
17
+ export type { MediaFlight, MediaFlightAnchorNode, MediaFlightController, MediaFlightHostProps, MediaSurfaceVideoOf, MediaVideoSlot, MediaVideoSlotProps, MediaFlightOptions, MediaSurfaceContent, MediaSurfaceImage, MediaSurfaceVideo, MeasuredRect, } from './types';
18
18
  export type { MediaSurfaceProps } from './MediaSurface';
19
19
  //# sourceMappingURL=index.web.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/index.web.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,aAAa,EACb,SAAS,EACT,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,WAAW,EACX,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/index.web.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,4BAA4B,EAC5B,cAAc,EACd,aAAa,EACb,SAAS,EACT,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,WAAW,EACX,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -157,7 +157,10 @@ export declare function flyBack(id: string): void;
157
157
  *
158
158
  * DO NOT replace this with a direct `unmountVideoView` call. It looks
159
159
  * equivalent, it is the half that does not matter, and the fix would disappear
160
- * silently. `MediaFlight.test.tsx` makes that substitution its control.
160
+ * silently. `MediaFlight.test.tsx` makes that substitution its control, and
161
+ * `scripts/probe-expo-video-listener-leak.mjs` measures the upstream defect
162
+ * this steps around — run it before touching any of this. The day it prints
163
+ * LEAK GONE, this commit and the tests that pin it can go.
161
164
  *
162
165
  * The cost is one frame in which the outgoing surface shows its poster instead
163
166
  * of video. It is already on its way out and the destination is live by then —
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/store.ts"],"names":[],"mappings":"AA2BA,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAUjC,OAAO,KAAK,EACV,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,IAAsB,CAAC;AAEhE;;;;;;;;;GASG;AACH,UAAU,YAAY;IACpB,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,SAAS,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,YAAY,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAChC,UAAU,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC;IACjD,uEAAuE;IACvE,WAAW,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC;CACnD;AAED,UAAU,QAAQ;IAChB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC5C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAClC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClC,SAAS,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,4BAA4B,EAAE,QAAQ,GAAG,SAAS,CAAC;CACxD;AAyDD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAMnE;AAED,wEAAwE;AACxE,wBAAgB,UAAU,IAAI,SAAS,WAAW,EAAE,CAEnD;AAED,wDAAwD;AACxD,wBAAgB,cAAc,IAAI,WAAW,CAAC,MAAM,CAAC,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAInF;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAWtE;AAED,oDAAoD;AACpD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,KAAK,CACnB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA4Df;AAuBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQ9C;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAyBxC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CA6B9C;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,IAAI,IAAI,CAOvC"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/store.ts"],"names":[],"mappings":"AA2BA,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAUjC,OAAO,KAAK,EACV,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,IAAsB,CAAC;AAEhE;;;;;;;;;GASG;AACH,UAAU,YAAY;IACpB,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,SAAS,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,YAAY,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAChC,UAAU,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC;IACjD,uEAAuE;IACvE,WAAW,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC;CACnD;AAED,UAAU,QAAQ;IAChB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC5C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAClC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClC,SAAS,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,4BAA4B,EAAE,QAAQ,GAAG,SAAS,CAAC;CACxD;AAyDD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAMnE;AAED,wEAAwE;AACxE,wBAAgB,UAAU,IAAI,SAAS,WAAW,EAAE,CAEnD;AAED,wDAAwD;AACxD,wBAAgB,cAAc,IAAI,WAAW,CAAC,MAAM,CAAC,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAInF;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAWtE;AAED,oDAAoD;AACpD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,KAAK,CACnB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA4Df;AAuBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQ9C;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAyBxC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CA6B9C;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,IAAI,IAAI,CAOvC"}
@@ -57,6 +57,18 @@ export interface MediaSurfaceVideo {
57
57
  }
58
58
  /** What a media surface paints: a still image, or a consumer-owned video. */
59
59
  export type MediaSurfaceContent = MediaSurfaceImage | MediaSurfaceVideo;
60
+ /**
61
+ * `MediaSurfaceVideo` carrying the consumer's OWN player type.
62
+ *
63
+ * Bloom transports that object; it never reads it. Narrowing it to
64
+ * `VideoPlayerLike` on the way through would be Bloom asserting something about
65
+ * cargo — and it would land on the consumer, who then has to cast the player
66
+ * back before handing it to expo-video's `VideoView`. A contract that pushes a
67
+ * consumer towards `as` ends up as an `as any` in someone's repo.
68
+ */
69
+ export interface MediaSurfaceVideoOf<P extends VideoPlayerLike> extends MediaSurfaceVideo {
70
+ player: P;
71
+ }
60
72
  /**
61
73
  * A media surface currently owned by the flight layer.
62
74
  *
@@ -216,7 +228,7 @@ export interface MediaFlightController {
216
228
  * INTRINSIC size (300x150) instead of stretching — so a slot that drops this
217
229
  * style paints a 300x150 video inside whatever box it was given. Spread it.
218
230
  */
219
- export interface MediaVideoSlotProps {
231
+ export interface MediaVideoSlotProps<P extends VideoPlayerLike = VideoPlayerLike> {
220
232
  /**
221
233
  * The player Bloom was given — never one Bloom created.
222
234
  *
@@ -227,7 +239,7 @@ export interface MediaVideoSlotProps {
227
239
  * anything else here — keeping the old player, unmounting instead — brings
228
240
  * that defect back. See `releaseFlight`.
229
241
  */
230
- player: VideoPlayerLike | null;
242
+ player: P | null;
231
243
  /** Fills the box. Spread it — see above. */
232
244
  style: StyleProp<ViewStyle>;
233
245
  contentFit: 'contain' | 'cover';
@@ -249,7 +261,7 @@ export interface MediaVideoSlotProps {
249
261
  * layer, compared by identity — a new function every render republishes every
250
262
  * render. Correct either way, wasteful if you skip it.
251
263
  */
252
- export type MediaVideoSlot = (props: MediaVideoSlotProps) => ReactNode;
264
+ export type MediaVideoSlot<P extends VideoPlayerLike = VideoPlayerLike> = (props: MediaVideoSlotProps<P>) => ReactNode;
253
265
  /**
254
266
  * A place a media surface may live: the feed row it starts in, the fullscreen
255
267
  * player it lands in, and every other end of a flight.
@@ -260,11 +272,16 @@ export type MediaVideoSlot = (props: MediaVideoSlotProps) => ReactNode;
260
272
  * ever leaving the document. On native a host renders its media inline, which
261
273
  * is what it has always done and what the platform makes correct there.
262
274
  */
263
- export interface MediaFlightHostProps {
275
+ export interface MediaFlightHostProps<P extends VideoPlayerLike = VideoPlayerLike> {
264
276
  /** The id this host shares with the flight and with the other end. */
265
277
  id: string;
266
- /** What to paint: a still image, or a consumer-owned expo-video player. */
267
- content: MediaSurfaceContent;
278
+ /**
279
+ * What to paint: a still image, or a consumer-owned expo-video player.
280
+ *
281
+ * The player's own type flows from here into {@link renderVideo}, so a slot
282
+ * gets back exactly what it put in and hands it to `VideoView` with no cast.
283
+ */
284
+ content: MediaSurfaceImage | MediaSurfaceVideoOf<P>;
268
285
  /** Style of the host BOX. Give it the radius and `overflow: 'hidden'` you want. */
269
286
  style?: StyleProp<ViewStyle>;
270
287
  /** How the media fills the box. Defaults to `'cover'`. */
@@ -273,7 +290,7 @@ export interface MediaFlightHostProps {
273
290
  * Paint the video yourself, keeping your own `ref` and expo-video props. See
274
291
  * {@link MediaVideoSlot}; memoise it.
275
292
  */
276
- renderVideo?: MediaVideoSlot;
293
+ renderVideo?: MediaVideoSlot<P>;
277
294
  /** Whether the video arm shows expo-video's own controls. Defaults to `false`. */
278
295
  nativeControls?: boolean;
279
296
  accessibilityLabel?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/media-flight/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/bloom",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "packageManager": "bun@1.3.14",
5
5
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
6
6
  "main": "lib/commonjs/index.js",
@@ -15,10 +15,27 @@ import { useCallback } from 'react';
15
15
  import { StyleSheet, View } from 'react-native';
16
16
 
17
17
  import { MediaSurface } from './MediaSurface';
18
+ import type { VideoPlayerLike } from './expo-video-module';
18
19
  import { registerAnchor } from './store';
19
- import type { MediaFlightHostProps } from './types';
20
+ import type { MediaFlightHostProps, MediaVideoSlot } from './types';
20
21
 
21
- export function MediaFlightHost({
22
+ /**
23
+ * Hand a consumer's slot to `MediaSurface`, which is not generic.
24
+ *
25
+ * SOUND, and the reason is the invariant this whole family is built on: the
26
+ * only player Bloom ever passes to a slot is the one it took out of that
27
+ * slot's own `content`. Bloom transports the object and never substitutes one,
28
+ * so the value really is a `P` at runtime — the assertion says what the types
29
+ * cannot follow through a `memo`'d component, and it lives HERE, once, rather
30
+ * than in every consumer's `renderVideo`.
31
+ */
32
+ function widenSlot<P extends VideoPlayerLike>(
33
+ slot: MediaVideoSlot<P> | undefined,
34
+ ): MediaVideoSlot | undefined {
35
+ return slot as MediaVideoSlot | undefined;
36
+ }
37
+
38
+ export function MediaFlightHost<P extends VideoPlayerLike = VideoPlayerLike>({
22
39
  id,
23
40
  content,
24
41
  style,
@@ -29,7 +46,7 @@ export function MediaFlightHost({
29
46
  surfaceType,
30
47
  pointerEvents,
31
48
  flightId,
32
- }: MediaFlightHostProps) {
49
+ }: MediaFlightHostProps<P>) {
33
50
  // A callback ref rather than an effect: the anchor must be measurable the
34
51
  // moment the box is on screen, and a tap can come one frame later.
35
52
  const setNode = useCallback(
@@ -42,7 +59,7 @@ export function MediaFlightHost({
42
59
  <MediaSurface
43
60
  content={content}
44
61
  contentFit={contentFit}
45
- renderVideo={renderVideo}
62
+ renderVideo={widenSlot(renderVideo)}
46
63
  nativeControls={nativeControls}
47
64
  accessibilityLabel={accessibilityLabel}
48
65
  surfaceType={surfaceType}
@@ -22,10 +22,18 @@ import {
22
22
  releaseMediaNode,
23
23
  type MediaNodeRender,
24
24
  } from './media-node.web';
25
+ import type { VideoPlayerLike } from './expo-video-module';
25
26
  import { handOffFlight, hasFlight, registerAnchor } from './store';
26
- import type { MediaFlightHostProps } from './types';
27
+ import type { MediaFlightHostProps, MediaVideoSlot } from './types';
27
28
 
28
- export function MediaFlightHost({
29
+ /**
30
+ * The web host publishes its slot to the layer, which paints every shared node
31
+ * through one non-generic `MediaSurface`. Widening is sound for the same reason
32
+ * as the native host's `widenSlot`: the only player Bloom ever hands a slot is
33
+ * the one it took out of that slot's own `content`. Bloom transports the
34
+ * object; it never substitutes one.
35
+ */
36
+ export function MediaFlightHost<P extends VideoPlayerLike = VideoPlayerLike>({
29
37
  id,
30
38
  content,
31
39
  style,
@@ -35,7 +43,7 @@ export function MediaFlightHost({
35
43
  accessibilityLabel,
36
44
  pointerEvents,
37
45
  flightId,
38
- }: MediaFlightHostProps) {
46
+ }: MediaFlightHostProps<P>) {
39
47
  // State rather than a ref, because the claim runs in a layout effect and an
40
48
  // effect cannot depend on a ref's mutation. One extra render at mount.
41
49
  const [node, setNode] = useState<View | null>(null);
@@ -49,7 +57,15 @@ export function MediaFlightHost({
49
57
  );
50
58
 
51
59
  const render = useMemo<MediaNodeRender>(
52
- () => ({ content, contentFit, renderVideo, nativeControls, accessibilityLabel, flightId }),
60
+ () => ({
61
+ content,
62
+ contentFit,
63
+ // See `widenSlot`: the player a slot gets back is the one it put in.
64
+ renderVideo: renderVideo as MediaVideoSlot | undefined,
65
+ nativeControls,
66
+ accessibilityLabel,
67
+ flightId,
68
+ }),
53
69
  [content, contentFit, renderVideo, nativeControls, accessibilityLabel, flightId],
54
70
  );
55
71
  const renderRef = useRef(render);
@@ -37,6 +37,7 @@ import { StyleSheet, type View } from 'react-native';
37
37
  import { createPortal } from 'react-dom';
38
38
  import Animated, { interpolate, useAnimatedStyle } from 'react-native-reanimated';
39
39
 
40
+ import { SLOT_FILL_TOLERANCE_PX } from './constants';
40
41
  import { OverlayRoot } from '../overlay';
41
42
  import { Portal } from '../portal/index.web';
42
43
  import { MediaSurface, type MediaSurfaceStyle } from './MediaSurface';
@@ -89,6 +90,22 @@ MediaFlightLayer.displayName = 'MediaFlightLayer';
89
90
  /** One media element, painted into the wrapper its id owns, and never again. */
90
91
  function SharedMediaNode({ node }: { node: MediaNodeView }) {
91
92
  const { content, contentFit, renderVideo, nativeControls, accessibilityLabel, flightId } = node.render;
93
+
94
+ // DOES THE MEDIA ACTUALLY FILL ITS BOX?
95
+ //
96
+ // Bloom hands a `renderVideo` slot a style that sizes a replaced element, and
97
+ // a slot that forgets to spread it paints a 300x150 `<video>` inside whatever
98
+ // box it was given — correct in every other respect, wrong on screen, and
99
+ // reported by nothing. That defect shipped once from Bloom's own view and
100
+ // cost a day to find; a consumer writing the slot can reproduce it in a line.
101
+ //
102
+ // Measured one frame after the commit, because layout has to have happened.
103
+ useEffect(() => {
104
+ if (process.env.NODE_ENV === 'production' || renderVideo === undefined) return undefined;
105
+ const frame = requestAnimationFrame(() => warnIfNotFilling(node.wrapper));
106
+ return () => cancelAnimationFrame(frame);
107
+ }, [node.wrapper, renderVideo]);
108
+
92
109
  return createPortal(
93
110
  <MediaSurface
94
111
  content={content}
@@ -103,6 +120,37 @@ function SharedMediaNode({ node }: { node: MediaNodeView }) {
103
120
  );
104
121
  }
105
122
 
123
+ let hasWarnedAboutFill = false;
124
+
125
+ /**
126
+ * One warning per module lifetime, and none in production — same mechanism as
127
+ * `warnExpoVideoUnavailable`.
128
+ *
129
+ * It compares the media element with the WRAPPER, not with a fixed size: the
130
+ * invariant is "the media fills the box it was given", and a box that happens
131
+ * to be 300x150 is not a defect.
132
+ */
133
+ function warnIfNotFilling(wrapper: HTMLElement): void {
134
+ if (hasWarnedAboutFill || !wrapper.isConnected) return;
135
+ const media = wrapper.querySelector('video');
136
+ if (media === null) return;
137
+ const box = wrapper.getBoundingClientRect();
138
+ const painted = media.getBoundingClientRect();
139
+ if (box.width === 0 || box.height === 0) return;
140
+ const off =
141
+ Math.abs(painted.width - box.width) > SLOT_FILL_TOLERANCE_PX ||
142
+ Math.abs(painted.height - box.height) > SLOT_FILL_TOLERANCE_PX;
143
+ if (!off) return;
144
+ hasWarnedAboutFill = true;
145
+ // eslint-disable-next-line no-console
146
+ console.warn(
147
+ `[Bloom] A \`renderVideo\` element is ${Math.round(painted.width)}x${Math.round(painted.height)} ` +
148
+ `inside a ${Math.round(box.width)}x${Math.round(box.height)} box. A DOM \`<video>\` is a ` +
149
+ 'REPLACED element: without an explicit size it paints at its intrinsic 300x150 whatever ' +
150
+ 'its inset says. Spread the `style` Bloom passes the slot.',
151
+ );
152
+ }
153
+
106
154
  /**
107
155
  * One flying surface. See `MediaFlightLayer.tsx` for why the size is animated
108
156
  * rather than scaled.
@@ -13,7 +13,7 @@
13
13
  * platform video surface the way it rounds an image, and the transform lands on
14
14
  * a node reanimated is allowed to drive (expo-video's own host is not one).
15
15
  */
16
- import { memo, useCallback, useState, type ComponentProps } from 'react';
16
+ import { memo, useCallback, useEffect, useRef, useState, type ComponentProps } from 'react';
17
17
  import {
18
18
  Platform,
19
19
  StyleSheet,
@@ -25,6 +25,7 @@ import {
25
25
  import { Image } from 'expo-image';
26
26
  import Animated from 'react-native-reanimated';
27
27
 
28
+ import { SLOT_IDENTITY_CHURN_LIMIT } from './constants';
28
29
  import { handOffFlight } from './store';
29
30
  import type { MediaSurfaceContent, MediaVideoSlot } from './types';
30
31
  import {
@@ -115,7 +116,10 @@ export interface MediaSurfaceProps {
115
116
  * algorithm pauses WITHOUT firing `pause` (measured in real Chrome, with
116
117
  * controls), so the element goes quiet and its later removal fires nothing.
117
118
  * Unbinding alone does not help: `unmountVideoView` never clears the handler
118
- * it installed. See `releaseFlight`.
119
+ * it installed. See `releaseFlight`, and
120
+ * `scripts/probe-expo-video-listener-leak.mjs`, which measures that in real
121
+ * Chrome against expo-video's real class — including that grafting the
122
+ * missing `_removeListeners` into it makes the leak go away.
119
123
  */
120
124
  detached?: boolean;
121
125
  }
@@ -145,6 +149,18 @@ export const MediaSurface = memo(function MediaSurface({
145
149
  // supports.
146
150
  const [mountedSurfaceType] = useState(surfaceType);
147
151
 
152
+ // A slot rebuilt on every render republishes this surface to the layer on
153
+ // every render, which nothing at runtime reports — the picture is correct and
154
+ // the app is doing work in proportion to how often its rows re-render. The
155
+ // counter lives in an effect rather than in render: a render-phase ref write
156
+ // makes the React Compiler bail on the whole component.
157
+ const slotChurn = useRef(0);
158
+ useEffect(() => {
159
+ if (renderVideo === undefined) return;
160
+ slotChurn.current += 1;
161
+ if (slotChurn.current === SLOT_IDENTITY_CHURN_LIMIT) warnSlotNotMemoised();
162
+ }, [renderVideo]);
163
+
148
164
  // Both arms report the same fact — "there is a picture here now" — because the
149
165
  // destination of a flight can be either, and a caller should not have to know
150
166
  // which one it wired. expo-video raises `onFirstFrameRender` from `loadeddata`
@@ -225,6 +241,24 @@ export const MediaSurface = memo(function MediaSurface({
225
241
 
226
242
  MediaSurface.displayName = 'MediaSurface';
227
243
 
244
+ let hasWarnedAboutSlotChurn = false;
245
+
246
+ /**
247
+ * One warning per module lifetime, and none in production — same mechanism as
248
+ * `warnExpoVideoUnavailable`.
249
+ */
250
+ function warnSlotNotMemoised(): void {
251
+ if (process.env.NODE_ENV === 'production' || hasWarnedAboutSlotChurn) return;
252
+ hasWarnedAboutSlotChurn = true;
253
+ // eslint-disable-next-line no-console
254
+ console.warn(
255
+ `[Bloom] A \`renderVideo\` slot changed identity ${SLOT_IDENTITY_CHURN_LIMIT} times. ` +
256
+ 'It is part of what a media host publishes to the flight layer and is compared by ' +
257
+ 'identity, so a slot rebuilt on every render republishes the surface on every render. ' +
258
+ 'Wrap it in `useCallback` with the props it actually reads.',
259
+ );
260
+ }
261
+
228
262
  /**
229
263
  * The STILL of a media item — a video's poster, or the image itself — and never
230
264
  * a video view.
@@ -30,3 +30,25 @@ export const SURFACE_MOUNT_TIMEOUT_MS = 250;
30
30
  * `flyBack(id)` and `releaseFlight(id)` are the escape hatches, and they are
31
31
  * the caller's to use.
32
32
  */
33
+
34
+ /**
35
+ * How many times a video slot may change identity before Bloom says so.
36
+ *
37
+ * `renderVideo` is part of what a host publishes to the layer and is compared
38
+ * by identity, so an unmemoised slot republishes on every render of the row it
39
+ * lives in — silently, and only under load. Five is well past any legitimate
40
+ * cause (a mute toggle, a fit change) and far below the churn of a slot rebuilt
41
+ * per render in a scrolling feed.
42
+ */
43
+ export const SLOT_IDENTITY_CHURN_LIMIT = 5;
44
+
45
+ /**
46
+ * How far a media element may sit from the box it is supposed to fill, in px,
47
+ * before Bloom says so.
48
+ *
49
+ * Two, not zero: subpixel layout and a box mid-animation both land within a
50
+ * pixel, and the defect this catches is not subtle — a `<video>` that never
51
+ * received Bloom's sizing style paints at its intrinsic 300x150 inside whatever
52
+ * box it was given.
53
+ */
54
+ export const SLOT_FILL_TOLERANCE_PX = 2;
@@ -27,6 +27,9 @@ export type {
27
27
  MediaFlightAnchorNode,
28
28
  MediaFlightController,
29
29
  MediaFlightHostProps,
30
+ MediaSurfaceVideoOf,
31
+ MediaVideoSlot,
32
+ MediaVideoSlotProps,
30
33
  MediaFlightOptions,
31
34
  MediaSurfaceContent,
32
35
  MediaSurfaceImage,
@@ -36,6 +36,9 @@ export type {
36
36
  MediaFlightAnchorNode,
37
37
  MediaFlightController,
38
38
  MediaFlightHostProps,
39
+ MediaSurfaceVideoOf,
40
+ MediaVideoSlot,
41
+ MediaVideoSlotProps,
39
42
  MediaFlightOptions,
40
43
  MediaSurfaceContent,
41
44
  MediaSurfaceImage,
@@ -432,7 +432,10 @@ export function flyBack(id: string): void {
432
432
  *
433
433
  * DO NOT replace this with a direct `unmountVideoView` call. It looks
434
434
  * equivalent, it is the half that does not matter, and the fix would disappear
435
- * silently. `MediaFlight.test.tsx` makes that substitution its control.
435
+ * silently. `MediaFlight.test.tsx` makes that substitution its control, and
436
+ * `scripts/probe-expo-video-listener-leak.mjs` measures the upstream defect
437
+ * this steps around — run it before touching any of this. The day it prints
438
+ * LEAK GONE, this commit and the tests that pin it can go.
436
439
  *
437
440
  * The cost is one frame in which the outgoing surface shows its poster instead
438
441
  * of video. It is already on its way out and the destination is live by then —
@@ -66,6 +66,19 @@ export interface MediaSurfaceVideo {
66
66
  /** What a media surface paints: a still image, or a consumer-owned video. */
67
67
  export type MediaSurfaceContent = MediaSurfaceImage | MediaSurfaceVideo;
68
68
 
69
+ /**
70
+ * `MediaSurfaceVideo` carrying the consumer's OWN player type.
71
+ *
72
+ * Bloom transports that object; it never reads it. Narrowing it to
73
+ * `VideoPlayerLike` on the way through would be Bloom asserting something about
74
+ * cargo — and it would land on the consumer, who then has to cast the player
75
+ * back before handing it to expo-video's `VideoView`. A contract that pushes a
76
+ * consumer towards `as` ends up as an `as any` in someone's repo.
77
+ */
78
+ export interface MediaSurfaceVideoOf<P extends VideoPlayerLike> extends MediaSurfaceVideo {
79
+ player: P;
80
+ }
81
+
69
82
  /**
70
83
  * A media surface currently owned by the flight layer.
71
84
  *
@@ -233,7 +246,7 @@ export interface MediaFlightController {
233
246
  * INTRINSIC size (300x150) instead of stretching — so a slot that drops this
234
247
  * style paints a 300x150 video inside whatever box it was given. Spread it.
235
248
  */
236
- export interface MediaVideoSlotProps {
249
+ export interface MediaVideoSlotProps<P extends VideoPlayerLike = VideoPlayerLike> {
237
250
  /**
238
251
  * The player Bloom was given — never one Bloom created.
239
252
  *
@@ -244,7 +257,7 @@ export interface MediaVideoSlotProps {
244
257
  * anything else here — keeping the old player, unmounting instead — brings
245
258
  * that defect back. See `releaseFlight`.
246
259
  */
247
- player: VideoPlayerLike | null;
260
+ player: P | null;
248
261
  /** Fills the box. Spread it — see above. */
249
262
  style: StyleProp<ViewStyle>;
250
263
  contentFit: 'contain' | 'cover';
@@ -267,7 +280,9 @@ export interface MediaVideoSlotProps {
267
280
  * layer, compared by identity — a new function every render republishes every
268
281
  * render. Correct either way, wasteful if you skip it.
269
282
  */
270
- export type MediaVideoSlot = (props: MediaVideoSlotProps) => ReactNode;
283
+ export type MediaVideoSlot<P extends VideoPlayerLike = VideoPlayerLike> = (
284
+ props: MediaVideoSlotProps<P>,
285
+ ) => ReactNode;
271
286
 
272
287
  /**
273
288
  * A place a media surface may live: the feed row it starts in, the fullscreen
@@ -279,11 +294,16 @@ export type MediaVideoSlot = (props: MediaVideoSlotProps) => ReactNode;
279
294
  * ever leaving the document. On native a host renders its media inline, which
280
295
  * is what it has always done and what the platform makes correct there.
281
296
  */
282
- export interface MediaFlightHostProps {
297
+ export interface MediaFlightHostProps<P extends VideoPlayerLike = VideoPlayerLike> {
283
298
  /** The id this host shares with the flight and with the other end. */
284
299
  id: string;
285
- /** What to paint: a still image, or a consumer-owned expo-video player. */
286
- content: MediaSurfaceContent;
300
+ /**
301
+ * What to paint: a still image, or a consumer-owned expo-video player.
302
+ *
303
+ * The player's own type flows from here into {@link renderVideo}, so a slot
304
+ * gets back exactly what it put in and hands it to `VideoView` with no cast.
305
+ */
306
+ content: MediaSurfaceImage | MediaSurfaceVideoOf<P>;
287
307
  /** Style of the host BOX. Give it the radius and `overflow: 'hidden'` you want. */
288
308
  style?: StyleProp<ViewStyle>;
289
309
  /** How the media fills the box. Defaults to `'cover'`. */
@@ -292,7 +312,7 @@ export interface MediaFlightHostProps {
292
312
  * Paint the video yourself, keeping your own `ref` and expo-video props. See
293
313
  * {@link MediaVideoSlot}; memoise it.
294
314
  */
295
- renderVideo?: MediaVideoSlot;
315
+ renderVideo?: MediaVideoSlot<P>;
296
316
  /** Whether the video arm shows expo-video's own controls. Defaults to `false`. */
297
317
  nativeControls?: boolean;
298
318
  accessibilityLabel?: string;