expo-video 3.0.9 → 3.0.10

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 (45) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/android/build.gradle +2 -2
  3. package/build/VideoView.web.d.ts.map +1 -1
  4. package/build/VideoView.web.js +43 -9
  5. package/build/VideoView.web.js.map +1 -1
  6. package/expo-module.config.json +1 -1
  7. package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9.aar → 3.0.10/expo.modules.video-3.0.10.aar} +0 -0
  8. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.aar.md5 +1 -0
  9. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.aar.sha1 +1 -0
  10. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.aar.sha256 +1 -0
  11. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.aar.sha512 +1 -0
  12. package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9.module → 3.0.10/expo.modules.video-3.0.10.module} +17 -17
  13. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.module.md5 +1 -0
  14. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.module.sha1 +1 -0
  15. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.module.sha256 +1 -0
  16. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.module.sha512 +1 -0
  17. package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9.pom → 3.0.10/expo.modules.video-3.0.10.pom} +1 -1
  18. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.pom.md5 +1 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.pom.sha1 +1 -0
  20. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.pom.sha256 +1 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.10/expo.modules.video-3.0.10.pom.sha512 +1 -0
  22. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml +4 -4
  23. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.md5 +1 -1
  24. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha1 +1 -1
  25. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha256 +1 -1
  26. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha512 +1 -1
  27. package/package.json +2 -2
  28. package/src/VideoView.web.tsx +57 -9
  29. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.aar.md5 +0 -1
  30. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.aar.sha1 +0 -1
  31. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.aar.sha256 +0 -1
  32. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.aar.sha512 +0 -1
  33. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.module.md5 +0 -1
  34. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.module.sha1 +0 -1
  35. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.module.sha256 +0 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.module.sha512 +0 -1
  37. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.pom.md5 +0 -1
  38. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.pom.sha1 +0 -1
  39. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.pom.sha256 +0 -1
  40. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.9/expo.modules.video-3.0.9.pom.sha512 +0 -1
  41. /package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9-sources.jar → 3.0.10/expo.modules.video-3.0.10-sources.jar} +0 -0
  42. /package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9-sources.jar.md5 → 3.0.10/expo.modules.video-3.0.10-sources.jar.md5} +0 -0
  43. /package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9-sources.jar.sha1 → 3.0.10/expo.modules.video-3.0.10-sources.jar.sha1} +0 -0
  44. /package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9-sources.jar.sha256 → 3.0.10/expo.modules.video-3.0.10-sources.jar.sha256} +0 -0
  45. /package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.0.9/expo.modules.video-3.0.9-sources.jar.sha512 → 3.0.10/expo.modules.video-3.0.10-sources.jar.sha512} +0 -0
package/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 3.0.10 — 2025-09-04
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [Web] Fix fullscreen enter/exit methods and listeners not working in Safari on iOS. ([#39320](https://github.com/expo/expo/pull/39320) by [@behenate](https://github.com/behenate))
18
+
13
19
  ## 3.0.9 — 2025-09-03
14
20
 
15
21
  ### 🛠 Breaking changes
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '3.0.9'
7
+ version = '3.0.10'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.video"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName '3.0.9'
13
+ versionName '3.0.10'
14
14
  }
15
15
  }
16
16
 
@@ -1 +1 @@
1
- {"version":3,"file":"VideoView.web.d.ts","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6D,MAAM,OAAO,CAAC;AAGlF,OAAO,WAA6B,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAsBxD,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,eAAO,MAAM,SAAS;aAAiC,WAAW;kDAqMhE,CAAC;AAEH,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"VideoView.web.d.ts","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6D,MAAM,OAAO,CAAC;AAGlF,OAAO,WAA6B,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA6BxD,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,eAAO,MAAM,SAAS;aAAiC,WAAW;kDA8OhE,CAAC;AAEH,eAAe,SAAS,CAAC"}
@@ -24,7 +24,7 @@ export const VideoView = forwardRef((props, ref) => {
24
24
  const videoRef = useRef(null);
25
25
  const mediaNodeRef = useRef(null);
26
26
  const hasToSetupAudioContext = useRef(false);
27
- const fullscreenChangeListener = useRef(null);
27
+ const fullscreenChangeListeners = useRef(null);
28
28
  const isWaitingForFirstFrame = useRef(false);
29
29
  /**
30
30
  * Audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.
@@ -45,10 +45,25 @@ export const VideoView = forwardRef((props, ref) => {
45
45
  }, [props.useAudioNodePlayback]);
46
46
  useImperativeHandle(ref, () => ({
47
47
  enterFullscreen: async () => {
48
- if (!props.allowsFullscreen) {
48
+ if (!props.allowsFullscreen || !videoRef.current) {
49
49
  return;
50
50
  }
51
- await videoRef.current?.requestFullscreen();
51
+ // Cast the video to any to avoid ts errors. Methods such as webkitRequestFullscreen,
52
+ // webkitEnterFullScreen, msRequestFullscreen are not typed even though they exist.
53
+ const video = videoRef.current;
54
+ if (video.requestFullscreen) {
55
+ await video.requestFullscreen();
56
+ }
57
+ else if (video.webkitRequestFullscreen) {
58
+ // @ts-ignore webkitRequestFullscreen can exist on Apple devices
59
+ await video.webkitRequestFullscreen();
60
+ }
61
+ else if (video.webkitEnterFullScreen) {
62
+ await video.webkitEnterFullScreen();
63
+ }
64
+ else if (video.msRequestFullscreen) {
65
+ await video.msRequestFullscreen();
66
+ }
52
67
  },
53
68
  exitFullscreen: async () => {
54
69
  await document.exitFullscreen();
@@ -149,14 +164,33 @@ export const VideoView = forwardRef((props, ref) => {
149
164
  }
150
165
  }
151
166
  function setupFullscreenListener() {
152
- fullscreenChangeListener.current = fullscreenListener;
153
- videoRef.current?.addEventListener('fullscreenchange', fullscreenChangeListener.current);
167
+ cleanupFullscreenListener();
168
+ const video = videoRef.current;
169
+ if (!video)
170
+ return;
171
+ const fullscreenListeners = {
172
+ default: fullscreenListener,
173
+ safariEnter: () => props.onFullscreenEnter?.(),
174
+ safariExit: () => props.onFullscreenExit?.(),
175
+ msListener: fullscreenListener,
176
+ };
177
+ fullscreenChangeListeners.current = fullscreenListeners;
178
+ // Standard Fullscreen API
179
+ video.addEventListener('fullscreenchange', fullscreenListeners.default);
180
+ // Safari (webkit)
181
+ video.addEventListener('webkitbeginfullscreen', fullscreenListeners.safariEnter);
182
+ video.addEventListener('webkitendfullscreen', fullscreenListeners.safariExit);
183
+ // IE11 (ms)
184
+ document.addEventListener('MSFullscreenChange', fullscreenListeners.msListener);
154
185
  }
155
186
  function cleanupFullscreenListener() {
156
- if (fullscreenChangeListener.current) {
157
- videoRef.current?.removeEventListener('fullscreenchange', fullscreenChangeListener.current);
158
- fullscreenChangeListener.current = null;
159
- }
187
+ const video = videoRef.current;
188
+ if (!video || !fullscreenChangeListeners.current)
189
+ return;
190
+ video.removeEventListener('fullscreenchange', fullscreenChangeListeners.current.default);
191
+ video.removeEventListener('webkitbeginfullscreen', fullscreenChangeListeners.current?.safariEnter);
192
+ video.removeEventListener('webkitendfullscreen', fullscreenChangeListeners.current.safariExit);
193
+ document.removeEventListener('MSFullscreenChange', fullscreenChangeListeners.current.msListener);
160
194
  }
161
195
  useEffect(() => {
162
196
  if (videoRef.current) {
@@ -1 +1 @@
1
- {"version":3,"file":"VideoView.web.js","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAoB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG9D,SAAS,kBAAkB;IACzB,OAAO,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1E,CAAC;AAED,SAAS,kBAAkB,CAAC,YAAiC;IAC3D,MAAM,YAAY,GAAG,YAAY,EAAE,UAAU,EAAE,IAAI,IAAI,CAAC;IAExD,IAAI,YAAY,IAAI,YAAY,EAAE,CAAC;QACjC,YAAY,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QAC5B,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,SAAS,CAAC,KAA8B;IAC/C,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAClD,qIAAqI;IACrI,OAAO,eAAsC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,QAAQ,CAAC,oBAAoB,KAAK,UAAU,CAAC;AAC7F,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,KAAgD,EAAE,GAAG,EAAE,EAAE;IAC5F,MAAM,QAAQ,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,MAAM,CAAqC,IAAI,CAAC,CAAC;IACtE,MAAM,sBAAsB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,wBAAwB,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IACnE,MAAM,sBAAsB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE7C;;;;;OAKG;IACH,MAAM,eAAe,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IAEtD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,CAAC,oBAAoB,EAAE,CAAC;YAC/B,sBAAsB,EAAE,CAAC;YACzB,gBAAgB,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,gBAAgB,EAAE,CAAC;QACrB,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAEjC,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,eAAe,EAAE,KAAK,IAAI,EAAE;YAC1B,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;gBAC5B,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC;QAC9C,CAAC;QACD,cAAc,EAAE,KAAK,IAAI,EAAE;YACzB,MAAM,QAAQ,CAAC,cAAc,EAAE,CAAC;QAClC,CAAC;QACD,qBAAqB,EAAE,KAAK,IAAI,EAAE;YAChC,MAAM,QAAQ,CAAC,OAAO,EAAE,uBAAuB,EAAE,CAAC;QACpD,CAAC;QACD,oBAAoB,EAAE,KAAK,IAAI,EAAE;YAC/B,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,oBAAoB,EAAE,CAAC;YACxC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,YAAY,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;oBAChE,OAAO,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;gBACnE,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,CAAC;gBACV,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,CAAC,uBAAuB,EAAE,EAAE,CAAC;QACpC,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,CAAC,sBAAsB,EAAE,EAAE,CAAC;QACnC,CAAC,CAAC;QACF,MAAM,WAAW,GAAG,GAAG,EAAE;YACvB,sBAAsB,CAAC,OAAO,GAAG,IAAI,CAAC;QACxC,CAAC,CAAC;QACF,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,IAAI,sBAAsB,CAAC,OAAO,EAAE,CAAC;gBACnC,KAAK,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC/B,CAAC;YACD,sBAAsB,CAAC,OAAO,GAAG,KAAK,CAAC;QACzC,CAAC,CAAC;QACF,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;QACrE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;QACrE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAC7D,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAE5D,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;YACxE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;YACxE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;YAChE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QACjE,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,sBAAsB,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAE5E,kHAAkH;IAClH,oCAAoC;IACpC,SAAS,gBAAgB;QACvB,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QACD,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC;QAC7C,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC;QAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;QAEvC,IAAI,YAAY,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;YAC9C,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CACV,uHAAuH,CACxH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,SAAS,gBAAgB;QACvB,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QACD,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC;QAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;QACvC,IAAI,YAAY,IAAI,SAAS,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YAClD,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED,SAAS,sBAAsB;QAC7B,IACE,CAAC,sBAAsB,CAAC,OAAO;YAC/B,CAAC,SAAS,CAAC,cAAc,CAAC,aAAa;YACvC,CAAC,QAAQ,CAAC,OAAO;YACjB,CAAC,KAAK,CAAC,oBAAoB,EAC3B,CAAC;YACD,OAAO;QACT,CAAC;QACD,MAAM,YAAY,GAAG,kBAAkB,EAAE,CAAC;QAE1C,gBAAgB,EAAE,CAAC;QACnB,eAAe,CAAC,OAAO,GAAG,YAAY,CAAC;QACvC,eAAe,CAAC,OAAO,GAAG,kBAAkB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACtE,YAAY,CAAC,OAAO,GAAG,YAAY;YACjC,CAAC,CAAC,YAAY,CAAC,wBAAwB,CAAC,QAAQ,CAAC,OAAO,CAAC;YACzD,CAAC,CAAC,IAAI,CAAC;QACT,gBAAgB,EAAE,CAAC;QACnB,sBAAsB,CAAC,OAAO,GAAG,KAAK,CAAC;IACzC,CAAC;IAED,SAAS,kBAAkB;QACzB,IAAI,QAAQ,CAAC,iBAAiB,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;YACpD,KAAK,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,SAAS,uBAAuB;QAC9B,wBAAwB,CAAC,OAAO,GAAG,kBAAkB,CAAC;QACtD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,kBAAkB,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAC3F,CAAC;IAED,SAAS,yBAAyB;QAChC,IAAI,wBAAwB,CAAC,OAAO,EAAE,CAAC;YACrC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,kBAAkB,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;YAC5F,wBAAwB,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrB,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACjD,CAAC;QACD,uBAAuB,EAAE,CAAC;QAC1B,gBAAgB,EAAE,CAAC;QAEnB,OAAO,GAAG,EAAE;YACV,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACnD,CAAC;YACD,yBAAyB,EAAE,CAAC;YAC5B,gBAAgB,EAAE,CAAC;QACrB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAEnB,OAAO,CACL,CAAC,KAAK,CACJ,QAAQ,CAAC,CAAC,KAAK,CAAC,cAAc,IAAI,IAAI,CAAC,CACvC,YAAY,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAClE,WAAW,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAC/B,KAAK,CAAC,CAAC;YACL,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,SAAS,EAAE,KAAK,CAAC,UAAU;SAC5B,CAAC,CACF,MAAM,CAAC,CAAC,GAAG,EAAE;YACX,sBAAsB,EAAE,CAAC;QAC3B,CAAC,CAAC;IACF,yFAAyF;IACzF,cAAc,CAAC,CAAC,GAAG,EAAE;YACnB,sBAAsB,EAAE,CAAC;QAC3B,CAAC,CAAC,CACF,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE;YACd,+EAA+E;YAC/E,6EAA6E;YAC7E,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpD,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC;gBAC1B,sBAAsB,CAAC,OAAO,GAAG,KAAK,CAAC,oBAAoB,IAAI,KAAK,CAAC;gBACrE,sBAAsB,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CACF,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CACvD,WAAW,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAC/B,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,EAC3C,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,eAAe,SAAS,CAAC","sourcesContent":["import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { StyleSheet } from 'react-native';\n\nimport VideoPlayer, { getSourceUri } from './VideoPlayer.web';\nimport type { VideoViewProps } from './VideoView.types';\n\nfunction createAudioContext(): AudioContext | null {\n return typeof window !== 'undefined' ? new window.AudioContext() : null;\n}\n\nfunction createZeroGainNode(audioContext: AudioContext | null): GainNode | null {\n const zeroGainNode = audioContext?.createGain() ?? null;\n\n if (audioContext && zeroGainNode) {\n zeroGainNode.gain.value = 0;\n zeroGainNode.connect(audioContext.destination);\n }\n return zeroGainNode;\n}\n\nfunction mapStyles(style: VideoViewProps['style']): React.CSSProperties {\n const flattenedStyles = StyleSheet.flatten(style);\n // Looking through react-native-web source code they also just pass styles directly without further conversions, so it's just a cast.\n return flattenedStyles as React.CSSProperties;\n}\n\nexport function isPictureInPictureSupported(): boolean {\n return typeof document === 'object' && typeof document.exitPictureInPicture === 'function';\n}\n\nexport const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoViewProps, ref) => {\n const videoRef = useRef<null | HTMLVideoElement>(null);\n const mediaNodeRef = useRef<null | MediaElementAudioSourceNode>(null);\n const hasToSetupAudioContext = useRef(false);\n const fullscreenChangeListener = useRef<null | (() => void)>(null);\n const isWaitingForFirstFrame = useRef(false);\n\n /**\n * Audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.\n * Using audio context nodes allows muting videos without displaying the mute icon in the video player.\n * We have to keep the context that called createMediaElementSource(videoRef), as the method can't be called\n * for the second time with another context and there is no way to unbind the video and audio context afterward.\n */\n const audioContextRef = useRef<null | AudioContext>(null);\n const zeroGainNodeRef = useRef<null | GainNode>(null);\n\n useEffect(() => {\n if (props.useAudioNodePlayback) {\n maybeSetupAudioContext();\n attachAudioNodes();\n } else {\n detachAudioNodes();\n }\n }, [props.useAudioNodePlayback]);\n\n useImperativeHandle(ref, () => ({\n enterFullscreen: async () => {\n if (!props.allowsFullscreen) {\n return;\n }\n await videoRef.current?.requestFullscreen();\n },\n exitFullscreen: async () => {\n await document.exitFullscreen();\n },\n startPictureInPicture: async () => {\n await videoRef.current?.requestPictureInPicture();\n },\n stopPictureInPicture: async () => {\n try {\n await document.exitPictureInPicture();\n } catch (e) {\n if (e instanceof DOMException && e.name === 'InvalidStateError') {\n console.warn('The VideoView is not in Picture-in-Picture mode.');\n } else {\n throw e;\n }\n }\n },\n }));\n\n useEffect(() => {\n const onEnter = () => {\n props.onPictureInPictureStart?.();\n };\n const onLeave = () => {\n props.onPictureInPictureStop?.();\n };\n const onLoadStart = () => {\n isWaitingForFirstFrame.current = true;\n };\n const onCanPlay = () => {\n if (isWaitingForFirstFrame.current) {\n props.onFirstFrameRender?.();\n }\n isWaitingForFirstFrame.current = false;\n };\n videoRef.current?.addEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.addEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.addEventListener('loadstart', onLoadStart);\n videoRef.current?.addEventListener('loadeddata', onCanPlay);\n\n return () => {\n videoRef.current?.removeEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.removeEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.removeEventListener('loadstart', onLoadStart);\n videoRef.current?.removeEventListener('loadeddata', onCanPlay);\n };\n }, [videoRef, props.onPictureInPictureStop, props.onPictureInPictureStart]);\n\n // Adds the video view as a candidate for being the audio source for the player (when multiple views play from one\n // player only one will emit audio).\n function attachAudioNodes() {\n if (!props.useAudioNodePlayback) {\n return;\n }\n const audioContext = audioContextRef.current;\n const zeroGainNode = zeroGainNodeRef.current;\n const mediaNode = mediaNodeRef.current;\n\n if (audioContext && zeroGainNode && mediaNode) {\n props.player.mountAudioNode(audioContext, zeroGainNode, mediaNode);\n } else {\n console.warn(\n \"Couldn't mount audio node, this might affect the audio playback when using multiple video views with the same player.\"\n );\n }\n }\n\n function detachAudioNodes() {\n if (!props.useAudioNodePlayback) {\n return;\n }\n const audioContext = audioContextRef.current;\n const mediaNode = mediaNodeRef.current;\n if (audioContext && mediaNode && videoRef.current) {\n props.player.unmountAudioNode(videoRef.current, audioContext, mediaNode);\n }\n }\n\n function maybeSetupAudioContext() {\n if (\n !hasToSetupAudioContext.current ||\n !navigator.userActivation.hasBeenActive ||\n !videoRef.current ||\n !props.useAudioNodePlayback\n ) {\n return;\n }\n const audioContext = createAudioContext();\n\n detachAudioNodes();\n audioContextRef.current = audioContext;\n zeroGainNodeRef.current = createZeroGainNode(audioContextRef.current);\n mediaNodeRef.current = audioContext\n ? audioContext.createMediaElementSource(videoRef.current)\n : null;\n attachAudioNodes();\n hasToSetupAudioContext.current = false;\n }\n\n function fullscreenListener() {\n if (document.fullscreenElement === videoRef.current) {\n props.onFullscreenEnter?.();\n } else {\n props.onFullscreenExit?.();\n }\n }\n\n function setupFullscreenListener() {\n fullscreenChangeListener.current = fullscreenListener;\n videoRef.current?.addEventListener('fullscreenchange', fullscreenChangeListener.current);\n }\n\n function cleanupFullscreenListener() {\n if (fullscreenChangeListener.current) {\n videoRef.current?.removeEventListener('fullscreenchange', fullscreenChangeListener.current);\n fullscreenChangeListener.current = null;\n }\n }\n\n useEffect(() => {\n if (videoRef.current) {\n props.player?.mountVideoView(videoRef.current);\n }\n setupFullscreenListener();\n attachAudioNodes();\n\n return () => {\n if (videoRef.current) {\n props.player?.unmountVideoView(videoRef.current);\n }\n cleanupFullscreenListener();\n detachAudioNodes();\n };\n }, [props.player]);\n\n return (\n <video\n controls={props.nativeControls ?? true}\n controlsList={props.allowsFullscreen ? undefined : 'nofullscreen'}\n crossOrigin={props.crossOrigin}\n style={{\n ...mapStyles(props.style),\n objectFit: props.contentFit,\n }}\n onPlay={() => {\n maybeSetupAudioContext();\n }}\n // The player can autoplay when muted, unmuting by a user should create the audio context\n onVolumeChange={() => {\n maybeSetupAudioContext();\n }}\n ref={(newRef) => {\n // This is called with a null value before `player.unmountVideoView` is called,\n // we can't assign null to videoRef if we want to unmount it from the player.\n if (newRef && !newRef.isEqualNode(videoRef.current)) {\n videoRef.current = newRef;\n hasToSetupAudioContext.current = props.useAudioNodePlayback ?? false;\n maybeSetupAudioContext();\n }\n }}\n disablePictureInPicture={!props.allowsPictureInPicture}\n playsInline={props.playsInline}\n src={getSourceUri(props.player?.src) ?? ''}\n />\n );\n});\n\nexport default VideoView;\n"]}
1
+ {"version":3,"file":"VideoView.web.js","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { StyleSheet } from 'react-native';\n\nimport VideoPlayer, { getSourceUri } from './VideoPlayer.web';\nimport type { VideoViewProps } from './VideoView.types';\n\ntype FullscreenChangeListeners = {\n default: () => void;\n safariEnter: () => void;\n safariExit: () => void;\n msListener: () => void;\n};\n\nfunction createAudioContext(): AudioContext | null {\n return typeof window !== 'undefined' ? new window.AudioContext() : null;\n}\n\nfunction createZeroGainNode(audioContext: AudioContext | null): GainNode | null {\n const zeroGainNode = audioContext?.createGain() ?? null;\n\n if (audioContext && zeroGainNode) {\n zeroGainNode.gain.value = 0;\n zeroGainNode.connect(audioContext.destination);\n }\n return zeroGainNode;\n}\n\nfunction mapStyles(style: VideoViewProps['style']): React.CSSProperties {\n const flattenedStyles = StyleSheet.flatten(style);\n // Looking through react-native-web source code they also just pass styles directly without further conversions, so it's just a cast.\n return flattenedStyles as React.CSSProperties;\n}\n\nexport function isPictureInPictureSupported(): boolean {\n return typeof document === 'object' && typeof document.exitPictureInPicture === 'function';\n}\n\nexport const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoViewProps, ref) => {\n const videoRef = useRef<null | HTMLVideoElement>(null);\n const mediaNodeRef = useRef<null | MediaElementAudioSourceNode>(null);\n const hasToSetupAudioContext = useRef(false);\n const fullscreenChangeListeners = useRef<null | FullscreenChangeListeners>(null);\n const isWaitingForFirstFrame = useRef(false);\n\n /**\n * Audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.\n * Using audio context nodes allows muting videos without displaying the mute icon in the video player.\n * We have to keep the context that called createMediaElementSource(videoRef), as the method can't be called\n * for the second time with another context and there is no way to unbind the video and audio context afterward.\n */\n const audioContextRef = useRef<null | AudioContext>(null);\n const zeroGainNodeRef = useRef<null | GainNode>(null);\n\n useEffect(() => {\n if (props.useAudioNodePlayback) {\n maybeSetupAudioContext();\n attachAudioNodes();\n } else {\n detachAudioNodes();\n }\n }, [props.useAudioNodePlayback]);\n\n useImperativeHandle(ref, () => ({\n enterFullscreen: async () => {\n if (!props.allowsFullscreen || !videoRef.current) {\n return;\n }\n // Cast the video to any to avoid ts errors. Methods such as webkitRequestFullscreen,\n // webkitEnterFullScreen, msRequestFullscreen are not typed even though they exist.\n const video = videoRef.current as any;\n\n if (video.requestFullscreen) {\n await video.requestFullscreen();\n } else if (video.webkitRequestFullscreen) {\n // @ts-ignore webkitRequestFullscreen can exist on Apple devices\n await video.webkitRequestFullscreen();\n } else if (video.webkitEnterFullScreen) {\n await video.webkitEnterFullScreen();\n } else if (video.msRequestFullscreen) {\n await video.msRequestFullscreen();\n }\n },\n exitFullscreen: async () => {\n await document.exitFullscreen();\n },\n startPictureInPicture: async () => {\n await videoRef.current?.requestPictureInPicture();\n },\n stopPictureInPicture: async () => {\n try {\n await document.exitPictureInPicture();\n } catch (e) {\n if (e instanceof DOMException && e.name === 'InvalidStateError') {\n console.warn('The VideoView is not in Picture-in-Picture mode.');\n } else {\n throw e;\n }\n }\n },\n }));\n\n useEffect(() => {\n const onEnter = () => {\n props.onPictureInPictureStart?.();\n };\n const onLeave = () => {\n props.onPictureInPictureStop?.();\n };\n const onLoadStart = () => {\n isWaitingForFirstFrame.current = true;\n };\n const onCanPlay = () => {\n if (isWaitingForFirstFrame.current) {\n props.onFirstFrameRender?.();\n }\n isWaitingForFirstFrame.current = false;\n };\n videoRef.current?.addEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.addEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.addEventListener('loadstart', onLoadStart);\n videoRef.current?.addEventListener('loadeddata', onCanPlay);\n\n return () => {\n videoRef.current?.removeEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.removeEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.removeEventListener('loadstart', onLoadStart);\n videoRef.current?.removeEventListener('loadeddata', onCanPlay);\n };\n }, [videoRef, props.onPictureInPictureStop, props.onPictureInPictureStart]);\n\n // Adds the video view as a candidate for being the audio source for the player (when multiple views play from one\n // player only one will emit audio).\n function attachAudioNodes() {\n if (!props.useAudioNodePlayback) {\n return;\n }\n const audioContext = audioContextRef.current;\n const zeroGainNode = zeroGainNodeRef.current;\n const mediaNode = mediaNodeRef.current;\n\n if (audioContext && zeroGainNode && mediaNode) {\n props.player.mountAudioNode(audioContext, zeroGainNode, mediaNode);\n } else {\n console.warn(\n \"Couldn't mount audio node, this might affect the audio playback when using multiple video views with the same player.\"\n );\n }\n }\n\n function detachAudioNodes() {\n if (!props.useAudioNodePlayback) {\n return;\n }\n const audioContext = audioContextRef.current;\n const mediaNode = mediaNodeRef.current;\n if (audioContext && mediaNode && videoRef.current) {\n props.player.unmountAudioNode(videoRef.current, audioContext, mediaNode);\n }\n }\n\n function maybeSetupAudioContext() {\n if (\n !hasToSetupAudioContext.current ||\n !navigator.userActivation.hasBeenActive ||\n !videoRef.current ||\n !props.useAudioNodePlayback\n ) {\n return;\n }\n const audioContext = createAudioContext();\n\n detachAudioNodes();\n audioContextRef.current = audioContext;\n zeroGainNodeRef.current = createZeroGainNode(audioContextRef.current);\n mediaNodeRef.current = audioContext\n ? audioContext.createMediaElementSource(videoRef.current)\n : null;\n attachAudioNodes();\n hasToSetupAudioContext.current = false;\n }\n\n function fullscreenListener() {\n if (document.fullscreenElement === videoRef.current) {\n props.onFullscreenEnter?.();\n } else {\n props.onFullscreenExit?.();\n }\n }\n\n function setupFullscreenListener() {\n cleanupFullscreenListener();\n const video = videoRef.current;\n if (!video) return;\n const fullscreenListeners: FullscreenChangeListeners = {\n default: fullscreenListener,\n safariEnter: () => props.onFullscreenEnter?.(),\n safariExit: () => props.onFullscreenExit?.(),\n msListener: fullscreenListener,\n };\n\n fullscreenChangeListeners.current = fullscreenListeners;\n\n // Standard Fullscreen API\n video.addEventListener('fullscreenchange', fullscreenListeners.default);\n\n // Safari (webkit)\n video.addEventListener('webkitbeginfullscreen', fullscreenListeners.safariEnter);\n video.addEventListener('webkitendfullscreen', fullscreenListeners.safariExit);\n\n // IE11 (ms)\n document.addEventListener('MSFullscreenChange', fullscreenListeners.msListener);\n }\n\n function cleanupFullscreenListener() {\n const video = videoRef.current;\n if (!video || !fullscreenChangeListeners.current) return;\n\n video.removeEventListener('fullscreenchange', fullscreenChangeListeners.current.default);\n video.removeEventListener(\n 'webkitbeginfullscreen',\n fullscreenChangeListeners.current?.safariEnter\n );\n video.removeEventListener('webkitendfullscreen', fullscreenChangeListeners.current.safariExit);\n document.removeEventListener(\n 'MSFullscreenChange',\n fullscreenChangeListeners.current.msListener\n );\n }\n\n useEffect(() => {\n if (videoRef.current) {\n props.player?.mountVideoView(videoRef.current);\n }\n setupFullscreenListener();\n attachAudioNodes();\n\n return () => {\n if (videoRef.current) {\n props.player?.unmountVideoView(videoRef.current);\n }\n cleanupFullscreenListener();\n detachAudioNodes();\n };\n }, [props.player]);\n\n return (\n <video\n controls={props.nativeControls ?? true}\n controlsList={props.allowsFullscreen ? undefined : 'nofullscreen'}\n crossOrigin={props.crossOrigin}\n style={{\n ...mapStyles(props.style),\n objectFit: props.contentFit,\n }}\n onPlay={() => {\n maybeSetupAudioContext();\n }}\n // The player can autoplay when muted, unmuting by a user should create the audio context\n onVolumeChange={() => {\n maybeSetupAudioContext();\n }}\n ref={(newRef) => {\n // This is called with a null value before `player.unmountVideoView` is called,\n // we can't assign null to videoRef if we want to unmount it from the player.\n if (newRef && !newRef.isEqualNode(videoRef.current)) {\n videoRef.current = newRef;\n hasToSetupAudioContext.current = props.useAudioNodePlayback ?? false;\n maybeSetupAudioContext();\n }\n }}\n disablePictureInPicture={!props.allowsPictureInPicture}\n playsInline={props.playsInline}\n src={getSourceUri(props.player?.src) ?? ''}\n />\n );\n});\n\nexport default VideoView;\n"]}
@@ -8,7 +8,7 @@
8
8
  "publication": {
9
9
  "groupId": "host.exp.exponent",
10
10
  "artifactId": "expo.modules.video",
11
- "version": "3.0.9",
11
+ "version": "3.0.10",
12
12
  "repository": "local-maven-repo"
13
13
  }
14
14
  }
@@ -0,0 +1 @@
1
+ de4d82e89ccd0d94a601a26a44ebfc0696e0bd70
@@ -0,0 +1 @@
1
+ 42073035495bb534c34347050767c72cd27cab426a3c28d5ba1ac06bf8cd9aa1
@@ -0,0 +1 @@
1
+ c09f69a702edac9b878f3b2daec4ec30c0029136c343b0b00d9fffd7aef9e9946d43a24d8d51e8a4c5fec5f03aae8ca2858bae296fd7b66c9539b8e16392c347
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.video",
6
- "version": "3.0.9",
6
+ "version": "3.0.10",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.video-3.0.9.aar",
28
- "url": "expo.modules.video-3.0.9.aar",
29
- "size": 499112,
30
- "sha512": "d7750e9155149c0131d04463b2eba12059ecc2bb5e5e3c800e90f5d5df85fe67d26b2e73ac96f8c5772212249ab2dcc4bbbc522bf3f1adad03e76337457155c3",
31
- "sha256": "87fe6f2ca3a6fca794a15b4555d1ea3cdd3b38c82c48b75fd03c4f55ec7941e0",
32
- "sha1": "5173ce3f6b4ff3693b325a6e0858571f9c162e38",
33
- "md5": "3a9f65c14799d372d9ff3cf1e911e79c"
27
+ "name": "expo.modules.video-3.0.10.aar",
28
+ "url": "expo.modules.video-3.0.10.aar",
29
+ "size": 499109,
30
+ "sha512": "c09f69a702edac9b878f3b2daec4ec30c0029136c343b0b00d9fffd7aef9e9946d43a24d8d51e8a4c5fec5f03aae8ca2858bae296fd7b66c9539b8e16392c347",
31
+ "sha256": "42073035495bb534c34347050767c72cd27cab426a3c28d5ba1ac06bf8cd9aa1",
32
+ "sha1": "de4d82e89ccd0d94a601a26a44ebfc0696e0bd70",
33
+ "md5": "4370033dcfecb3eb931316a56a2ae911"
34
34
  }
35
35
  ]
36
36
  },
@@ -113,13 +113,13 @@
113
113
  ],
114
114
  "files": [
115
115
  {
116
- "name": "expo.modules.video-3.0.9.aar",
117
- "url": "expo.modules.video-3.0.9.aar",
118
- "size": 499112,
119
- "sha512": "d7750e9155149c0131d04463b2eba12059ecc2bb5e5e3c800e90f5d5df85fe67d26b2e73ac96f8c5772212249ab2dcc4bbbc522bf3f1adad03e76337457155c3",
120
- "sha256": "87fe6f2ca3a6fca794a15b4555d1ea3cdd3b38c82c48b75fd03c4f55ec7941e0",
121
- "sha1": "5173ce3f6b4ff3693b325a6e0858571f9c162e38",
122
- "md5": "3a9f65c14799d372d9ff3cf1e911e79c"
116
+ "name": "expo.modules.video-3.0.10.aar",
117
+ "url": "expo.modules.video-3.0.10.aar",
118
+ "size": 499109,
119
+ "sha512": "c09f69a702edac9b878f3b2daec4ec30c0029136c343b0b00d9fffd7aef9e9946d43a24d8d51e8a4c5fec5f03aae8ca2858bae296fd7b66c9539b8e16392c347",
120
+ "sha256": "42073035495bb534c34347050767c72cd27cab426a3c28d5ba1ac06bf8cd9aa1",
121
+ "sha1": "de4d82e89ccd0d94a601a26a44ebfc0696e0bd70",
122
+ "md5": "4370033dcfecb3eb931316a56a2ae911"
123
123
  }
124
124
  ]
125
125
  },
@@ -133,8 +133,8 @@
133
133
  },
134
134
  "files": [
135
135
  {
136
- "name": "expo.modules.video-3.0.9-sources.jar",
137
- "url": "expo.modules.video-3.0.9-sources.jar",
136
+ "name": "expo.modules.video-3.0.10-sources.jar",
137
+ "url": "expo.modules.video-3.0.10-sources.jar",
138
138
  "size": 63354,
139
139
  "sha512": "f04da6fb2f5c1ba96f029687f46e31c261026c814121a81cdc257af33fa1383b5de77a8862123e632c93888bfce716edcc2acdd0d07460c678917139a9901b11",
140
140
  "sha256": "5efae1439855a664269c02cbb19144477c94a4ac3af7dbd6a7874739d851cd4b",
@@ -0,0 +1 @@
1
+ 2b84a50ff9a7876de406f3f8ca184c496dd7575b2adab26b47427b38dd144a5c
@@ -0,0 +1 @@
1
+ ac671e3f2cf649c8bda5cd3b18b3da3ad67e50e2c15c0deb1283287f4771a435921bcd17ec888f1b02524ab24e9785cfa09b9461935c11b09a66faa33fdf09e5
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>host.exp.exponent</groupId>
11
11
  <artifactId>expo.modules.video</artifactId>
12
- <version>3.0.9</version>
12
+ <version>3.0.10</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.video</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ acd2ba6426f0e986c00bc4fe24fe385619c7c41c
@@ -0,0 +1 @@
1
+ d07392689aa8a6721550c6da2138c7ffeadd61d1d29fc47c6970074eccd79ec8
@@ -0,0 +1 @@
1
+ d8fc8bafd12e53f95787dd6b6402967e690674c5e3ab6eed183dbc91fc88616659002774cc3227be66185eed5336299938eb28951695373710a2f2464c71cea2
@@ -3,11 +3,11 @@
3
3
  <groupId>host.exp.exponent</groupId>
4
4
  <artifactId>expo.modules.video</artifactId>
5
5
  <versioning>
6
- <latest>3.0.9</latest>
7
- <release>3.0.9</release>
6
+ <latest>3.0.10</latest>
7
+ <release>3.0.10</release>
8
8
  <versions>
9
- <version>3.0.9</version>
9
+ <version>3.0.10</version>
10
10
  </versions>
11
- <lastUpdated>20250903164526</lastUpdated>
11
+ <lastUpdated>20250904150716</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 9cbafe00b1787bf123b208f6c7e368c7
1
+ 0fb7d17a8edffaa8ee48a8709f7f6dca
@@ -1 +1 @@
1
- 31942a54bb947a12b7bc87fdb7a1b07e28f1e7af
1
+ f229fb8dedc06833d16a6d7999b5e6bfbf3c520e
@@ -1 +1 @@
1
- 11c276d67c072d486db31682e60d28633be98d889375b781f3c34713da773eb4
1
+ 9bd232a97850733d9904d4c3a35f2ecd3d2d6c2fe9a735343c6aaccf5b115101
@@ -1 +1 @@
1
- 43a94228aa7fab8da9f52521497995dad645c764a73d6ef1186103cce7986c1d0fa4347a61d0a0e2d1840b8db961935def842c02c174c7fe17fe03f2c29bb6bc
1
+ 0116a51bbf7573570a8182de37f7f0f86c20c86e8e3f78fe012549a2a0483b72a1bd5d2b5df7fbedd7582bc42d90f1da1f7ff72963574dcab9d1a17d1ffd2fe3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-video",
3
3
  "title": "Expo Video",
4
- "version": "3.0.9",
4
+ "version": "3.0.10",
5
5
  "description": "A cross-platform, performant video component for React Native and Expo with Web support",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
@@ -38,5 +38,5 @@
38
38
  "react": "*",
39
39
  "react-native": "*"
40
40
  },
41
- "gitHead": "f97e6d1cae0cb14d5eed1b08fad6d12a7144af3a"
41
+ "gitHead": "8cafaff8076e443e6c80e8013ec809f4f290f24d"
42
42
  }
@@ -4,6 +4,13 @@ import { StyleSheet } from 'react-native';
4
4
  import VideoPlayer, { getSourceUri } from './VideoPlayer.web';
5
5
  import type { VideoViewProps } from './VideoView.types';
6
6
 
7
+ type FullscreenChangeListeners = {
8
+ default: () => void;
9
+ safariEnter: () => void;
10
+ safariExit: () => void;
11
+ msListener: () => void;
12
+ };
13
+
7
14
  function createAudioContext(): AudioContext | null {
8
15
  return typeof window !== 'undefined' ? new window.AudioContext() : null;
9
16
  }
@@ -32,7 +39,7 @@ export const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoView
32
39
  const videoRef = useRef<null | HTMLVideoElement>(null);
33
40
  const mediaNodeRef = useRef<null | MediaElementAudioSourceNode>(null);
34
41
  const hasToSetupAudioContext = useRef(false);
35
- const fullscreenChangeListener = useRef<null | (() => void)>(null);
42
+ const fullscreenChangeListeners = useRef<null | FullscreenChangeListeners>(null);
36
43
  const isWaitingForFirstFrame = useRef(false);
37
44
 
38
45
  /**
@@ -55,10 +62,23 @@ export const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoView
55
62
 
56
63
  useImperativeHandle(ref, () => ({
57
64
  enterFullscreen: async () => {
58
- if (!props.allowsFullscreen) {
65
+ if (!props.allowsFullscreen || !videoRef.current) {
59
66
  return;
60
67
  }
61
- await videoRef.current?.requestFullscreen();
68
+ // Cast the video to any to avoid ts errors. Methods such as webkitRequestFullscreen,
69
+ // webkitEnterFullScreen, msRequestFullscreen are not typed even though they exist.
70
+ const video = videoRef.current as any;
71
+
72
+ if (video.requestFullscreen) {
73
+ await video.requestFullscreen();
74
+ } else if (video.webkitRequestFullscreen) {
75
+ // @ts-ignore webkitRequestFullscreen can exist on Apple devices
76
+ await video.webkitRequestFullscreen();
77
+ } else if (video.webkitEnterFullScreen) {
78
+ await video.webkitEnterFullScreen();
79
+ } else if (video.msRequestFullscreen) {
80
+ await video.msRequestFullscreen();
81
+ }
62
82
  },
63
83
  exitFullscreen: async () => {
64
84
  await document.exitFullscreen();
@@ -168,15 +188,43 @@ export const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoView
168
188
  }
169
189
 
170
190
  function setupFullscreenListener() {
171
- fullscreenChangeListener.current = fullscreenListener;
172
- videoRef.current?.addEventListener('fullscreenchange', fullscreenChangeListener.current);
191
+ cleanupFullscreenListener();
192
+ const video = videoRef.current;
193
+ if (!video) return;
194
+ const fullscreenListeners: FullscreenChangeListeners = {
195
+ default: fullscreenListener,
196
+ safariEnter: () => props.onFullscreenEnter?.(),
197
+ safariExit: () => props.onFullscreenExit?.(),
198
+ msListener: fullscreenListener,
199
+ };
200
+
201
+ fullscreenChangeListeners.current = fullscreenListeners;
202
+
203
+ // Standard Fullscreen API
204
+ video.addEventListener('fullscreenchange', fullscreenListeners.default);
205
+
206
+ // Safari (webkit)
207
+ video.addEventListener('webkitbeginfullscreen', fullscreenListeners.safariEnter);
208
+ video.addEventListener('webkitendfullscreen', fullscreenListeners.safariExit);
209
+
210
+ // IE11 (ms)
211
+ document.addEventListener('MSFullscreenChange', fullscreenListeners.msListener);
173
212
  }
174
213
 
175
214
  function cleanupFullscreenListener() {
176
- if (fullscreenChangeListener.current) {
177
- videoRef.current?.removeEventListener('fullscreenchange', fullscreenChangeListener.current);
178
- fullscreenChangeListener.current = null;
179
- }
215
+ const video = videoRef.current;
216
+ if (!video || !fullscreenChangeListeners.current) return;
217
+
218
+ video.removeEventListener('fullscreenchange', fullscreenChangeListeners.current.default);
219
+ video.removeEventListener(
220
+ 'webkitbeginfullscreen',
221
+ fullscreenChangeListeners.current?.safariEnter
222
+ );
223
+ video.removeEventListener('webkitendfullscreen', fullscreenChangeListeners.current.safariExit);
224
+ document.removeEventListener(
225
+ 'MSFullscreenChange',
226
+ fullscreenChangeListeners.current.msListener
227
+ );
180
228
  }
181
229
 
182
230
  useEffect(() => {
@@ -1 +0,0 @@
1
- 5173ce3f6b4ff3693b325a6e0858571f9c162e38
@@ -1 +0,0 @@
1
- 87fe6f2ca3a6fca794a15b4555d1ea3cdd3b38c82c48b75fd03c4f55ec7941e0
@@ -1 +0,0 @@
1
- d7750e9155149c0131d04463b2eba12059ecc2bb5e5e3c800e90f5d5df85fe67d26b2e73ac96f8c5772212249ab2dcc4bbbc522bf3f1adad03e76337457155c3
@@ -1 +0,0 @@
1
- bcfdcd2863cb298115305a36039218cbdf1e734b
@@ -1 +0,0 @@
1
- a3b7d4fac71a2213fe70909b50efa3b53b0337997839223162f049a372e18955
@@ -1 +0,0 @@
1
- 9ce60e3520dc6b2f3fa0eb62a0e0c3204c8d575043cd8544bc7b0ffbf68722597c0d7a0d221b87f92174833292216bdd15c1ad87e3840297b7ec87e7b02cd169
@@ -1 +0,0 @@
1
- 0c7b1efb0eeee962d4bf702c2ff3979e7e1e11cd
@@ -1 +0,0 @@
1
- 61fcc640dddd65c9ae1d73f7d7fa9868a915dd873a74266323310fec53dfeb97
@@ -1 +0,0 @@
1
- aa9e0a17692fe2e4e09198ff75ff7ef4231db7041b74daf28b2c7a4dd3088133fb0237417548fd817622fde5677452e97ef168682c23d6e0aa10f1ea0113ddd7