@stepincto/expo-video 1.0.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 (180) hide show
  1. package/README.md +45 -0
  2. package/android/build.gradle +32 -0
  3. package/android/src/main/AndroidManifest.xml +20 -0
  4. package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +241 -0
  5. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +145 -0
  6. package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -0
  7. package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -0
  8. package/android/src/main/java/expo/modules/video/PictureInPictureHelperFragment.kt +26 -0
  9. package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +36 -0
  10. package/android/src/main/java/expo/modules/video/VideoCache.kt +104 -0
  11. package/android/src/main/java/expo/modules/video/VideoExceptions.kt +34 -0
  12. package/android/src/main/java/expo/modules/video/VideoManager.kt +133 -0
  13. package/android/src/main/java/expo/modules/video/VideoModule.kt +414 -0
  14. package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -0
  15. package/android/src/main/java/expo/modules/video/VideoView.kt +367 -0
  16. package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
  17. package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +217 -0
  18. package/android/src/main/java/expo/modules/video/enums/AudioMixingMode.kt +20 -0
  19. package/android/src/main/java/expo/modules/video/enums/ContentFit.kt +19 -0
  20. package/android/src/main/java/expo/modules/video/enums/ContentType.kt +22 -0
  21. package/android/src/main/java/expo/modules/video/enums/DRMType.kt +26 -0
  22. package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -0
  23. package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +184 -0
  24. package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +39 -0
  25. package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
  26. package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +93 -0
  27. package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +164 -0
  28. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +460 -0
  29. package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +125 -0
  30. package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +32 -0
  31. package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +525 -0
  32. package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +125 -0
  33. package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +15 -0
  34. package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +25 -0
  35. package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +19 -0
  36. package/android/src/main/java/expo/modules/video/records/Tracks.kt +81 -0
  37. package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -0
  38. package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +12 -0
  39. package/android/src/main/java/expo/modules/video/records/VideoSize.kt +14 -0
  40. package/android/src/main/java/expo/modules/video/records/VideoSource.kt +104 -0
  41. package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +24 -0
  42. package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +75 -0
  43. package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
  44. package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +15 -0
  45. package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -0
  46. package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -0
  47. package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -0
  48. package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -0
  49. package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -0
  50. package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -0
  51. package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -0
  52. package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -0
  53. package/android/src/main/res/layout/fullscreen_player_activity.xml +16 -0
  54. package/android/src/main/res/layout/surface_player_view.xml +7 -0
  55. package/android/src/main/res/layout/texture_player_view.xml +7 -0
  56. package/android/src/main/res/values/styles.xml +9 -0
  57. package/app.plugin.js +1 -0
  58. package/build/NativeVideoModule.d.ts +16 -0
  59. package/build/NativeVideoModule.d.ts.map +1 -0
  60. package/build/NativeVideoModule.js +3 -0
  61. package/build/NativeVideoModule.js.map +1 -0
  62. package/build/NativeVideoModule.web.d.ts +3 -0
  63. package/build/NativeVideoModule.web.d.ts.map +1 -0
  64. package/build/NativeVideoModule.web.js +2 -0
  65. package/build/NativeVideoModule.web.js.map +1 -0
  66. package/build/NativeVideoView.d.ts +4 -0
  67. package/build/NativeVideoView.d.ts.map +1 -0
  68. package/build/NativeVideoView.js +6 -0
  69. package/build/NativeVideoView.js.map +1 -0
  70. package/build/VideoModule.d.ts +38 -0
  71. package/build/VideoModule.d.ts.map +1 -0
  72. package/build/VideoModule.js +53 -0
  73. package/build/VideoModule.js.map +1 -0
  74. package/build/VideoPlayer.d.ts +15 -0
  75. package/build/VideoPlayer.d.ts.map +1 -0
  76. package/build/VideoPlayer.js +52 -0
  77. package/build/VideoPlayer.js.map +1 -0
  78. package/build/VideoPlayer.types.d.ts +532 -0
  79. package/build/VideoPlayer.types.d.ts.map +1 -0
  80. package/build/VideoPlayer.types.js +2 -0
  81. package/build/VideoPlayer.types.js.map +1 -0
  82. package/build/VideoPlayer.web.d.ts +75 -0
  83. package/build/VideoPlayer.web.d.ts.map +1 -0
  84. package/build/VideoPlayer.web.js +376 -0
  85. package/build/VideoPlayer.web.js.map +1 -0
  86. package/build/VideoPlayerEvents.types.d.ts +262 -0
  87. package/build/VideoPlayerEvents.types.d.ts.map +1 -0
  88. package/build/VideoPlayerEvents.types.js +2 -0
  89. package/build/VideoPlayerEvents.types.js.map +1 -0
  90. package/build/VideoThumbnail.d.ts +29 -0
  91. package/build/VideoThumbnail.d.ts.map +1 -0
  92. package/build/VideoThumbnail.js +3 -0
  93. package/build/VideoThumbnail.js.map +1 -0
  94. package/build/VideoView.d.ts +44 -0
  95. package/build/VideoView.d.ts.map +1 -0
  96. package/build/VideoView.js +76 -0
  97. package/build/VideoView.js.map +1 -0
  98. package/build/VideoView.types.d.ts +147 -0
  99. package/build/VideoView.types.d.ts.map +1 -0
  100. package/build/VideoView.types.js +2 -0
  101. package/build/VideoView.types.js.map +1 -0
  102. package/build/VideoView.web.d.ts +9 -0
  103. package/build/VideoView.web.d.ts.map +1 -0
  104. package/build/VideoView.web.js +180 -0
  105. package/build/VideoView.web.js.map +1 -0
  106. package/build/index.d.ts +9 -0
  107. package/build/index.d.ts.map +1 -0
  108. package/build/index.js +7 -0
  109. package/build/index.js.map +1 -0
  110. package/build/resolveAssetSource.d.ts +3 -0
  111. package/build/resolveAssetSource.d.ts.map +1 -0
  112. package/build/resolveAssetSource.js +3 -0
  113. package/build/resolveAssetSource.js.map +1 -0
  114. package/build/resolveAssetSource.web.d.ts +4 -0
  115. package/build/resolveAssetSource.web.d.ts.map +1 -0
  116. package/build/resolveAssetSource.web.js +16 -0
  117. package/build/resolveAssetSource.web.js.map +1 -0
  118. package/expo-module.config.json +9 -0
  119. package/ios/Cache/CachableRequest.swift +44 -0
  120. package/ios/Cache/CachedResource.swift +97 -0
  121. package/ios/Cache/CachingHelpers.swift +92 -0
  122. package/ios/Cache/MediaFileHandle.swift +94 -0
  123. package/ios/Cache/MediaInfo.swift +147 -0
  124. package/ios/Cache/ResourceLoaderDelegate.swift +274 -0
  125. package/ios/Cache/SynchronizedHashTable.swift +23 -0
  126. package/ios/Cache/VideoCacheManager.swift +338 -0
  127. package/ios/ContentKeyDelegate.swift +214 -0
  128. package/ios/ContentKeyManager.swift +21 -0
  129. package/ios/Enums/AudioMixingMode.swift +37 -0
  130. package/ios/Enums/ContentType.swift +12 -0
  131. package/ios/Enums/DRMType.swift +20 -0
  132. package/ios/Enums/PlayerStatus.swift +10 -0
  133. package/ios/Enums/VideoContentFit.swift +39 -0
  134. package/ios/ExpoVideo.podspec +29 -0
  135. package/ios/NowPlayingManager.swift +296 -0
  136. package/ios/Records/BufferOptions.swift +12 -0
  137. package/ios/Records/DRMOptions.swift +24 -0
  138. package/ios/Records/PlaybackError.swift +10 -0
  139. package/ios/Records/Tracks.swift +176 -0
  140. package/ios/Records/VideoEventPayloads.swift +76 -0
  141. package/ios/Records/VideoMetadata.swift +16 -0
  142. package/ios/Records/VideoSize.swift +15 -0
  143. package/ios/Records/VideoSource.swift +25 -0
  144. package/ios/Thumbnails/VideoThumbnail.swift +27 -0
  145. package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
  146. package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
  147. package/ios/VideoAsset.swift +123 -0
  148. package/ios/VideoExceptions.swift +53 -0
  149. package/ios/VideoItem.swift +11 -0
  150. package/ios/VideoManager.swift +140 -0
  151. package/ios/VideoModule.swift +383 -0
  152. package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
  153. package/ios/VideoPlayer.swift +435 -0
  154. package/ios/VideoPlayerAudioTracks.swift +72 -0
  155. package/ios/VideoPlayerItem.swift +97 -0
  156. package/ios/VideoPlayerObserver.swift +523 -0
  157. package/ios/VideoPlayerSubtitles.swift +71 -0
  158. package/ios/VideoSourceLoader.swift +89 -0
  159. package/ios/VideoSourceLoaderListener.swift +34 -0
  160. package/ios/VideoView.swift +224 -0
  161. package/package.json +59 -0
  162. package/plugin/build/tsconfig.tsbuildinfo +1 -0
  163. package/plugin/build/withExpoVideo.d.ts +7 -0
  164. package/plugin/build/withExpoVideo.js +38 -0
  165. package/src/NativeVideoModule.ts +20 -0
  166. package/src/NativeVideoModule.web.ts +1 -0
  167. package/src/NativeVideoView.ts +8 -0
  168. package/src/VideoModule.ts +59 -0
  169. package/src/VideoPlayer.tsx +67 -0
  170. package/src/VideoPlayer.types.ts +613 -0
  171. package/src/VideoPlayer.web.tsx +451 -0
  172. package/src/VideoPlayerEvents.types.ts +313 -0
  173. package/src/VideoThumbnail.ts +31 -0
  174. package/src/VideoView.tsx +86 -0
  175. package/src/VideoView.types.ts +165 -0
  176. package/src/VideoView.web.tsx +214 -0
  177. package/src/index.ts +46 -0
  178. package/src/resolveAssetSource.ts +2 -0
  179. package/src/resolveAssetSource.web.ts +17 -0
  180. package/src/ts-declarations/react-native-assets.d.ts +1 -0
@@ -0,0 +1,180 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
3
+ import { StyleSheet } from 'react-native';
4
+ import { getSourceUri } from './VideoPlayer.web';
5
+ function createAudioContext() {
6
+ return typeof window !== 'undefined' ? new window.AudioContext() : null;
7
+ }
8
+ function createZeroGainNode(audioContext) {
9
+ const zeroGainNode = audioContext?.createGain() ?? null;
10
+ if (audioContext && zeroGainNode) {
11
+ zeroGainNode.gain.value = 0;
12
+ zeroGainNode.connect(audioContext.destination);
13
+ }
14
+ return zeroGainNode;
15
+ }
16
+ function mapStyles(style) {
17
+ const flattenedStyles = StyleSheet.flatten(style);
18
+ // Looking through react-native-web source code they also just pass styles directly without further conversions, so it's just a cast.
19
+ return flattenedStyles;
20
+ }
21
+ export function isPictureInPictureSupported() {
22
+ return typeof document === 'object' && typeof document.exitPictureInPicture === 'function';
23
+ }
24
+ export const VideoView = forwardRef((props, ref) => {
25
+ const videoRef = useRef(null);
26
+ const mediaNodeRef = useRef(null);
27
+ const hasToSetupAudioContext = useRef(false);
28
+ const fullscreenChangeListener = useRef(null);
29
+ const isWaitingForFirstFrame = useRef(false);
30
+ /**
31
+ * Audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.
32
+ * Using audio context nodes allows muting videos without displaying the mute icon in the video player.
33
+ * We have to keep the context that called createMediaElementSource(videoRef), as the method can't be called
34
+ * for the second time with another context and there is no way to unbind the video and audio context afterward.
35
+ */
36
+ const audioContextRef = useRef(null);
37
+ const zeroGainNodeRef = useRef(null);
38
+ useImperativeHandle(ref, () => ({
39
+ enterFullscreen: async () => {
40
+ if (!props.allowsFullscreen) {
41
+ return;
42
+ }
43
+ await videoRef.current?.requestFullscreen();
44
+ },
45
+ exitFullscreen: async () => {
46
+ await document.exitFullscreen();
47
+ },
48
+ startPictureInPicture: async () => {
49
+ await videoRef.current?.requestPictureInPicture();
50
+ },
51
+ stopPictureInPicture: async () => {
52
+ try {
53
+ await document.exitPictureInPicture();
54
+ }
55
+ catch (e) {
56
+ if (e instanceof DOMException && e.name === 'InvalidStateError') {
57
+ console.warn('The VideoView is not in Picture-in-Picture mode.');
58
+ }
59
+ else {
60
+ throw e;
61
+ }
62
+ }
63
+ },
64
+ }));
65
+ useEffect(() => {
66
+ const onEnter = () => {
67
+ props.onPictureInPictureStart?.();
68
+ };
69
+ const onLeave = () => {
70
+ props.onPictureInPictureStop?.();
71
+ };
72
+ const onLoadStart = () => {
73
+ isWaitingForFirstFrame.current = true;
74
+ };
75
+ const onCanPlay = () => {
76
+ if (isWaitingForFirstFrame.current) {
77
+ props.onFirstFrameRender?.();
78
+ }
79
+ isWaitingForFirstFrame.current = false;
80
+ };
81
+ videoRef.current?.addEventListener('enterpictureinpicture', onEnter);
82
+ videoRef.current?.addEventListener('leavepictureinpicture', onLeave);
83
+ videoRef.current?.addEventListener('loadstart', onLoadStart);
84
+ videoRef.current?.addEventListener('loadeddata', onCanPlay);
85
+ return () => {
86
+ videoRef.current?.removeEventListener('enterpictureinpicture', onEnter);
87
+ videoRef.current?.removeEventListener('leavepictureinpicture', onLeave);
88
+ videoRef.current?.removeEventListener('loadstart', onLoadStart);
89
+ videoRef.current?.removeEventListener('loadeddata', onCanPlay);
90
+ };
91
+ }, [videoRef, props.onPictureInPictureStop, props.onPictureInPictureStart]);
92
+ // Adds the video view as a candidate for being the audio source for the player (when multiple views play from one
93
+ // player only one will emit audio).
94
+ function attachAudioNodes() {
95
+ const audioContext = audioContextRef.current;
96
+ const zeroGainNode = zeroGainNodeRef.current;
97
+ const mediaNode = mediaNodeRef.current;
98
+ if (audioContext && zeroGainNode && mediaNode) {
99
+ props.player.mountAudioNode(audioContext, zeroGainNode, mediaNode);
100
+ }
101
+ else {
102
+ console.warn("Couldn't mount audio node, this might affect the audio playback when using multiple video views with the same player.");
103
+ }
104
+ }
105
+ function detachAudioNodes() {
106
+ const audioContext = audioContextRef.current;
107
+ const mediaNode = mediaNodeRef.current;
108
+ if (audioContext && mediaNode && videoRef.current) {
109
+ props.player.unmountAudioNode(videoRef.current, audioContext, mediaNode);
110
+ }
111
+ }
112
+ function maybeSetupAudioContext() {
113
+ if (!hasToSetupAudioContext.current ||
114
+ !navigator.userActivation.hasBeenActive ||
115
+ !videoRef.current) {
116
+ return;
117
+ }
118
+ const audioContext = createAudioContext();
119
+ detachAudioNodes();
120
+ audioContextRef.current = audioContext;
121
+ zeroGainNodeRef.current = createZeroGainNode(audioContextRef.current);
122
+ mediaNodeRef.current = audioContext
123
+ ? audioContext.createMediaElementSource(videoRef.current)
124
+ : null;
125
+ attachAudioNodes();
126
+ hasToSetupAudioContext.current = false;
127
+ }
128
+ function fullscreenListener() {
129
+ if (document.fullscreenElement === videoRef.current) {
130
+ props.onFullscreenEnter?.();
131
+ }
132
+ else {
133
+ props.onFullscreenExit?.();
134
+ }
135
+ }
136
+ function setupFullscreenListener() {
137
+ fullscreenChangeListener.current = fullscreenListener;
138
+ videoRef.current?.addEventListener('fullscreenchange', fullscreenChangeListener.current);
139
+ }
140
+ function cleanupFullscreenListener() {
141
+ if (fullscreenChangeListener.current) {
142
+ videoRef.current?.removeEventListener('fullscreenchange', fullscreenChangeListener.current);
143
+ fullscreenChangeListener.current = null;
144
+ }
145
+ }
146
+ useEffect(() => {
147
+ if (videoRef.current) {
148
+ props.player?.mountVideoView(videoRef.current);
149
+ }
150
+ setupFullscreenListener();
151
+ attachAudioNodes();
152
+ return () => {
153
+ if (videoRef.current) {
154
+ props.player?.unmountVideoView(videoRef.current);
155
+ }
156
+ cleanupFullscreenListener();
157
+ detachAudioNodes();
158
+ };
159
+ }, [props.player]);
160
+ return (_jsx("video", { controls: props.nativeControls ?? true, controlsList: props.allowsFullscreen ? undefined : 'nofullscreen', crossOrigin: props.crossOrigin, style: {
161
+ ...mapStyles(props.style),
162
+ objectFit: props.contentFit,
163
+ }, onPlay: () => {
164
+ maybeSetupAudioContext();
165
+ },
166
+ // The player can autoplay when muted, unmuting by a user should create the audio context
167
+ onVolumeChange: () => {
168
+ maybeSetupAudioContext();
169
+ }, ref: (newRef) => {
170
+ // This is called with a null value before `player.unmountVideoView` is called,
171
+ // we can't assign null to videoRef if we want to unmount it from the player.
172
+ if (newRef && !newRef.isEqualNode(videoRef.current)) {
173
+ videoRef.current = newRef;
174
+ hasToSetupAudioContext.current = true;
175
+ maybeSetupAudioContext();
176
+ }
177
+ }, disablePictureInPicture: !props.allowsPictureInPicture, playsInline: props.playsInline, src: getSourceUri(props.player?.src) ?? '' }));
178
+ });
179
+ export default VideoView;
180
+ //# sourceMappingURL=VideoView.web.js.map
@@ -0,0 +1 @@
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\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 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 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 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 ) {\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 = true;\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"]}
@@ -0,0 +1,9 @@
1
+ export { isPictureInPictureSupported, clearVideoCacheAsync, setVideoCacheSizeAsync, getCurrentVideoCacheSize, preCacheVideoPartialAsync, preCacheVideoAsync, isVideoCachedAsync, } from './VideoModule';
2
+ export { VideoView } from './VideoView';
3
+ export { useVideoPlayer } from './VideoPlayer';
4
+ export { VideoContentFit, VideoViewProps, SurfaceType } from './VideoView.types';
5
+ export { VideoThumbnail } from './VideoThumbnail';
6
+ export { createVideoPlayer } from './VideoPlayer';
7
+ export { VideoPlayer, VideoPlayerStatus, VideoSource, PlayerError, VideoMetadata, DRMType, DRMOptions, BufferOptions, AudioMixingMode, VideoThumbnailOptions, VideoSize, SubtitleTrack, AudioTrack, VideoTrack, ContentType, } from './VideoPlayer.types';
8
+ export { VideoPlayerEvents, StatusChangeEventPayload, PlayingChangeEventPayload, PlaybackRateChangeEventPayload, VolumeChangeEventPayload, MutedChangeEventPayload, TimeUpdateEventPayload, SourceChangeEventPayload, SourceLoadEventPayload, } from './VideoPlayerEvents.types';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,aAAa,EACb,OAAO,EACP,UAAU,EACV,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,SAAS,EACT,aAAa,EACb,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,yBAAyB,EACzB,8BAA8B,EAC9B,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { isPictureInPictureSupported, clearVideoCacheAsync, setVideoCacheSizeAsync, getCurrentVideoCacheSize, preCacheVideoPartialAsync, preCacheVideoAsync, isVideoCachedAsync, } from './VideoModule';
2
+ export { VideoView } from './VideoView';
3
+ export { useVideoPlayer } from './VideoPlayer';
4
+ export { VideoThumbnail } from './VideoThumbnail';
5
+ export { createVideoPlayer } from './VideoPlayer';
6
+ export { VideoPlayer, } from './VideoPlayer.types';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EACL,WAAW,GAeZ,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n isPictureInPictureSupported,\n clearVideoCacheAsync,\n setVideoCacheSizeAsync,\n getCurrentVideoCacheSize,\n preCacheVideoPartialAsync,\n preCacheVideoAsync,\n isVideoCachedAsync,\n} from './VideoModule';\nexport { VideoView } from './VideoView';\nexport { useVideoPlayer } from './VideoPlayer';\n\nexport { VideoContentFit, VideoViewProps, SurfaceType } from './VideoView.types';\nexport { VideoThumbnail } from './VideoThumbnail';\n\nexport { createVideoPlayer } from './VideoPlayer';\n\nexport {\n VideoPlayer,\n VideoPlayerStatus,\n VideoSource,\n PlayerError,\n VideoMetadata,\n DRMType,\n DRMOptions,\n BufferOptions,\n AudioMixingMode,\n VideoThumbnailOptions,\n VideoSize,\n SubtitleTrack,\n AudioTrack,\n VideoTrack,\n ContentType,\n} from './VideoPlayer.types';\n\nexport {\n VideoPlayerEvents,\n StatusChangeEventPayload,\n PlayingChangeEventPayload,\n PlaybackRateChangeEventPayload,\n VolumeChangeEventPayload,\n MutedChangeEventPayload,\n TimeUpdateEventPayload,\n SourceChangeEventPayload,\n SourceLoadEventPayload,\n} from './VideoPlayerEvents.types';\n"]}
@@ -0,0 +1,3 @@
1
+ import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
2
+ export default resolveAssetSource;
3
+ //# sourceMappingURL=resolveAssetSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveAssetSource.d.ts","sourceRoot":"","sources":["../src/resolveAssetSource.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,iDAAiD,CAAC;AACjF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
2
+ export default resolveAssetSource;
3
+ //# sourceMappingURL=resolveAssetSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveAssetSource.js","sourceRoot":"","sources":["../src/resolveAssetSource.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,iDAAiD,CAAC;AACjF,eAAe,kBAAkB,CAAC","sourcesContent":["import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';\nexport default resolveAssetSource;\n"]}
@@ -0,0 +1,4 @@
1
+ export default function resolveAssetSource(assetId: number): {
2
+ uri: string;
3
+ } | null;
4
+ //# sourceMappingURL=resolveAssetSource.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveAssetSource.web.d.ts","sourceRoot":"","sources":["../src/resolveAssetSource.web.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAalF"}
@@ -0,0 +1,16 @@
1
+ import { getAssetByID } from '@react-native/assets-registry/registry';
2
+ // Minimal `resolveAssetSource` implementation for video on web, based on the version from `expo-asset`
3
+ export default function resolveAssetSource(assetId) {
4
+ const asset = getAssetByID(assetId);
5
+ if (!asset) {
6
+ return null;
7
+ }
8
+ const type = !asset.type ? '' : `.${asset.type}`;
9
+ const assetPath = __DEV__
10
+ ? asset.httpServerLocation + '/' + asset.name + type
11
+ : asset.httpServerLocation.replace(/\.\.\//g, '_') + '/' + asset.name + type;
12
+ // The base has to have a valid syntax but doesn't matter - it's removed below as we use a relative path
13
+ const fromUrl = new URL(assetPath, 'https://expo.dev');
14
+ return { uri: fromUrl.toString().replace(fromUrl.origin, '') };
15
+ }
16
+ //# sourceMappingURL=resolveAssetSource.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveAssetSource.web.js","sourceRoot":"","sources":["../src/resolveAssetSource.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAEtE,uGAAuG;AACvG,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,OAAe;IACxD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,SAAS,GAAG,OAAO;QACvB,CAAC,CAAC,KAAK,CAAC,kBAAkB,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,IAAI;QACpD,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAE/E,wGAAwG;IACxG,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IACvD,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;AACjE,CAAC","sourcesContent":["import { getAssetByID } from '@react-native/assets-registry/registry';\n\n// Minimal `resolveAssetSource` implementation for video on web, based on the version from `expo-asset`\nexport default function resolveAssetSource(assetId: number): { uri: string } | null {\n const asset = getAssetByID(assetId);\n if (!asset) {\n return null;\n }\n const type = !asset.type ? '' : `.${asset.type}`;\n const assetPath = __DEV__\n ? asset.httpServerLocation + '/' + asset.name + type\n : asset.httpServerLocation.replace(/\\.\\.\\//g, '_') + '/' + asset.name + type;\n\n // The base has to have a valid syntax but doesn't matter - it's removed below as we use a relative path\n const fromUrl = new URL(assetPath, 'https://expo.dev');\n return { uri: fromUrl.toString().replace(fromUrl.origin, '') };\n}\n"]}
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": {
4
+ "modules": ["VideoModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.video.VideoModule"]
8
+ }
9
+ }
@@ -0,0 +1,44 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import AVFoundation
5
+ import UIKit
6
+ import CoreServices
7
+ import ExpoModulesCore
8
+
9
+ /**
10
+ * Class used for easier management of loadingRequests and associating them with data tasks.
11
+ * After the request is fulfilled or canceled, the data chunk should be saved to file holding the cache
12
+ */
13
+ class CachableRequest: Equatable, Hashable {
14
+ let loadingRequest: AVAssetResourceLoadingRequest
15
+ let dataTask: URLSessionDataTask
16
+ var dataRequest: AVAssetResourceLoadingDataRequest
17
+ var response: URLResponse?
18
+ private(set) var receivedData = Data()
19
+ private let dataOffset: Int64
20
+
21
+ init(loadingRequest: AVAssetResourceLoadingRequest, dataTask: URLSessionDataTask, dataRequest: AVAssetResourceLoadingDataRequest) {
22
+ self.loadingRequest = loadingRequest
23
+ self.dataTask = dataTask
24
+ self.dataRequest = dataRequest
25
+ self.dataOffset = dataRequest.requestedOffset
26
+ }
27
+
28
+ func onReceivedData(data: Data) {
29
+ receivedData.append(data)
30
+ }
31
+
32
+ func saveData(to cachedResource: CachedResource) {
33
+ // Disabled: Player is now read-only with respect to the cache.
34
+ // Do nothing.
35
+ }
36
+
37
+ static func == (lhs: CachableRequest, rhs: CachableRequest) -> Bool {
38
+ return lhs === rhs
39
+ }
40
+
41
+ func hash(into hasher: inout Hasher) {
42
+ hasher.combine(ObjectIdentifier(self))
43
+ }
44
+ }
@@ -0,0 +1,97 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import AVFoundation
5
+ import ExpoModulesCore
6
+
7
+ /**
8
+ * Class used to manage a single cached resource. Stores a fileHandle to saved video data, and
9
+ * allows saving/loading data used to correctly decode the data.
10
+ */
11
+ class CachedResource {
12
+ private let url: URL
13
+ private let dataPath: String
14
+ private let fileHandle: MediaFileHandle
15
+ private(set) var mediaInfo: MediaInfo?
16
+
17
+ init(dataFileUrl: String, resourceUrl: URL, dataPath: String) {
18
+ self.dataPath = dataPath
19
+ self.url = resourceUrl
20
+ self.fileHandle = MediaFileHandle(filePath: dataFileUrl)
21
+ self.mediaInfo = MediaInfo(forResourceUrl: resourceUrl)
22
+ }
23
+
24
+ func onResponseReceived(response: HTTPURLResponse) {
25
+ guard let headers = response.allHeaderFields as? [String: String], mediaInfo == nil, response.statusCode == 200 else {
26
+ return
27
+ }
28
+
29
+ mediaInfo = MediaInfo(
30
+ expectedContentLength: response.expectedContentLength,
31
+ mimeType: response.mimeType,
32
+ supportsByteRangeAccess: urlResponseSupportsByteRangeAcces(response),
33
+ headerFields: headers,
34
+ savePath: dataPath + VideoCacheManager.mediaInfoSuffix
35
+ )
36
+ mediaInfo?.saveToFile()
37
+ }
38
+
39
+ func fill(forLoadingRequest request: AVAssetResourceLoadingRequest) {
40
+ guard let mediaInfo, let mimeType = mediaInfo.mimeType else {
41
+ return
42
+ }
43
+ let fakeResponse = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: mediaInfo.headerFields)
44
+ let contentType = UTType(mimeType: mimeType)?.identifier
45
+ if let contentType {
46
+ request.contentInformationRequest?.contentType = contentType
47
+ request.contentInformationRequest?.contentLength = mediaInfo.expectedContentLength
48
+ request.contentInformationRequest?.isByteRangeAccessSupported = mediaInfo.supportsByteRangeAccess
49
+ request.response = fakeResponse
50
+ }
51
+ }
52
+
53
+ func writeData(data: Data, offset: Int64) async {
54
+ do {
55
+ mediaInfo?.addDataRange(newDataRange: (Int(offset), Int(offset) + data.count))
56
+ mediaInfo?.saveToFile()
57
+ try fileHandle.write(data: data, atOffset: Int(offset))
58
+ } catch {
59
+ log.warn("Failed to write at offset with the file handle")
60
+ }
61
+ }
62
+
63
+ func requestData(from: Int64, to: Int64) -> Data? {
64
+ if canRespondWithData(from: from, to: to) {
65
+ return fileHandle.readData(withOffset: Int(from), forLength: Int(to - from) + 1)
66
+ }
67
+ return nil
68
+ }
69
+
70
+ func requestBeginningOfData(from: Int64, to: Int64) -> Data? {
71
+ guard let dataRange = mediaInfo?.loadedDataRanges.first(where: { dataStart, dataEnd in
72
+ from >= dataStart && from < dataEnd
73
+ }) else {
74
+ return nil
75
+ }
76
+
77
+ return fileHandle.readData(withOffset: Int(from), forLength: Int(dataRange.1 - Int(from)) + 1)
78
+ }
79
+
80
+ func canRespondWithData(from: Int64, to: Int64) -> Bool {
81
+ guard let loadedDataRanges = mediaInfo?.loadedDataRanges else {
82
+ return false
83
+ }
84
+ return loadedDataRanges.contains(where: { loadedDataRangeStart, loadedDataRangeEnd in
85
+ from >= loadedDataRangeStart && to <= loadedDataRangeEnd
86
+ })
87
+ }
88
+
89
+ private func urlResponseSupportsByteRangeAcces(_ urlResponse: HTTPURLResponse) -> Bool {
90
+ // The first option is the standard-correct one, but we can check for some more in case someone
91
+ // didn't follow the documention fully when implementing the server
92
+ return urlResponse.allHeaderFields["Accept-Ranges"] as? String == "bytes" ||
93
+ urlResponse.allHeaderFields["Accept-Ranges"] as? String == "Bytes" ||
94
+ urlResponse.allHeaderFields["accept-ranges"] as? String == "bytes" ||
95
+ urlResponse.allHeaderFields["accept-ranges"] as? String == "Bytes"
96
+ }
97
+ }
@@ -0,0 +1,92 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import AVFoundation
5
+ import CryptoKit
6
+ import MobileCoreServices
7
+
8
+ internal func getRangeFromResponse(response: HTTPURLResponse) -> (Int?, Int?) {
9
+ // Sometimes the server returns under non-capitalized letters
10
+ let fullString = (response.allHeaderFields["Content-Range"] ?? response.allHeaderFields["content-range"]) as? String
11
+ // Example string: bytes 0-158008373/158008374
12
+ let fullStringComponents = fullString?.components(separatedBy: "/").first?.components(separatedBy: " ")
13
+ guard let rangeStringComponents = fullStringComponents?[safe: 1]?.components(separatedBy: "-") else {
14
+ return (nil, nil)
15
+ }
16
+ let first = rangeStringComponents.first ?? ""
17
+ let second = rangeStringComponents.count > 1 ? rangeStringComponents[1] : ""
18
+
19
+ return (Int(first), Int(second))
20
+ }
21
+
22
+ internal func getRangeFromRequest(request: URLRequest) -> (Int?, Int?) {
23
+ guard let fullString = request.allHTTPHeaderFields?["Range"] else {
24
+ return (nil, nil)
25
+ }
26
+ let rangeString = fullString.replacingOccurrences(of: "bytes=", with: "")
27
+ let rangeStringComponents = rangeString.split(separator: "-", maxSplits: 1, omittingEmptySubsequences: false)
28
+ let first = rangeStringComponents.first ?? ""
29
+ let second = rangeStringComponents.count > 1 ? rangeStringComponents[1] : ""
30
+
31
+ return (Int(first), Int(second))
32
+ }
33
+
34
+ internal func mimeTypeToExtension(mimeType: String?) -> String? {
35
+ guard let mimeType else {
36
+ return nil
37
+ }
38
+
39
+ guard let mimeUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as CFString, nil)?.takeUnretainedValue() else {
40
+ return nil
41
+ }
42
+ return UTTypeCopyPreferredTagWithClass(mimeUTI, kUTTagClassFilenameExtension)?.takeRetainedValue() as? String
43
+ }
44
+
45
+ internal extension Int {
46
+ var MB: Int { return self * 1024 * 1024 }
47
+ }
48
+
49
+ internal extension Array {
50
+ subscript(safe index: Int) -> Element? {
51
+ return indices.contains(index) ? self[index] : nil
52
+ }
53
+ }
54
+
55
+ internal extension Data {
56
+ func subdata(request: URLRequest, response: HTTPURLResponse) -> Data? {
57
+ let (requestStart, requestEnd) = getRangeFromRequest(request: request)
58
+ let (responseStart, responseEnd) = getRangeFromResponse(response: response)
59
+
60
+ guard let responseStart, let responseEnd, let requestStart else {
61
+ return nil
62
+ }
63
+ // Error handling checks would actually occur here, similar to Objective-C version
64
+ guard requestStart >= responseStart, requestStart < responseEnd else {
65
+ return nil
66
+ }
67
+
68
+ let startIndex = requestStart - responseStart // Corrected start index calculation
69
+ var endIndex: Int // Define endIndex outside of conditional logic
70
+
71
+ if let requestEnd = requestEnd {
72
+ endIndex = startIndex + (requestEnd - requestStart) + 1 // Use safely unwrapped requestEnd
73
+ } else {
74
+ // If requestEnd is nil, assume data to the end of resource is needed
75
+ endIndex = self.count
76
+ }
77
+
78
+ if endIndex > self.count {
79
+ return nil
80
+ }
81
+
82
+ return self.subdata(in: startIndex..<endIndex)
83
+ }
84
+ }
85
+
86
+ internal extension URL {
87
+ func withScheme(_ scheme: String) -> URL? {
88
+ var components = URLComponents(url: self, resolvingAgainstBaseURL: false)
89
+ components?.scheme = scheme
90
+ return components?.url
91
+ }
92
+ }
@@ -0,0 +1,94 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import ExpoModulesCore
5
+
6
+ /**
7
+ * Class meant to be used as a helper for handling files by the ResourceLoaderDelegate.
8
+ * All operations should be dispatched from the ResourceLoaderDelegate queue.
9
+ */
10
+ internal class MediaFileHandle {
11
+ private let filePath: String
12
+ private let lock = NSLock()
13
+ private lazy var readHandle = FileHandle(forReadingAtPath: filePath)
14
+ private lazy var writeHandle = FileHandle(forWritingAtPath: filePath)
15
+
16
+ var attributes: [FileAttributeKey: Any]? {
17
+ do {
18
+ return try FileManager.default.attributesOfItem(atPath: filePath)
19
+ } catch let error as NSError {
20
+ log.warn("An error occured while reading the file attributes at \(filePath) error: \(error)")
21
+ }
22
+ return nil
23
+ }
24
+
25
+ var fileSize: Int {
26
+ return attributes?[.size] as? Int ?? 0
27
+ }
28
+
29
+ private var fileUrl: URL? {
30
+ URL(string: filePath)
31
+ }
32
+
33
+ init(filePath: String) {
34
+ self.filePath = filePath
35
+
36
+ if let fileUrl {
37
+ VideoCacheManager.shared.registerOpenFile(at: fileUrl)
38
+ }
39
+
40
+ if !FileManager.default.fileExists(atPath: filePath) {
41
+ FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
42
+ }
43
+ }
44
+
45
+ deinit {
46
+ if let fileUrl {
47
+ VideoCacheManager.shared.unregisterOpenFile(at: fileUrl)
48
+ }
49
+ guard FileManager.default.fileExists(atPath: filePath) else {
50
+ return
51
+ }
52
+
53
+ close()
54
+ }
55
+
56
+ func readData(withOffset offset: Int, forLength length: Int) -> Data? {
57
+ lock.lock()
58
+ defer { lock.unlock() }
59
+
60
+ readHandle?.seek(toFileOffset: UInt64(offset))
61
+ return readHandle?.readData(ofLength: length)
62
+ }
63
+
64
+ func write(data: Data, atOffset offset: Int) throws {
65
+ lock.lock()
66
+ defer { lock.unlock() }
67
+
68
+ guard let writeHandle = writeHandle else {
69
+ return
70
+ }
71
+
72
+ try writeHandle.seek(toOffset: UInt64(offset))
73
+
74
+ writeHandle.write(data)
75
+ writeHandle.synchronizeFile()
76
+ }
77
+
78
+ func append(data: Data) {
79
+ lock.lock()
80
+ defer { lock.unlock() }
81
+ guard let writeHandle = writeHandle else {
82
+ return
83
+ }
84
+
85
+ writeHandle.seekToEndOfFile()
86
+ writeHandle.write(data)
87
+ writeHandle.synchronizeFile()
88
+ }
89
+
90
+ func close() {
91
+ readHandle?.closeFile()
92
+ writeHandle?.closeFile()
93
+ }
94
+ }