@stepincto/expo-video 1.0.1 → 1.0.4

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.
@@ -17,6 +17,8 @@ class CachableRequest: Equatable, Hashable {
17
17
  var response: URLResponse?
18
18
  private(set) var receivedData = Data()
19
19
  private let dataOffset: Int64
20
+ private static var fileLocks: [String: NSLock] = [:]
21
+ private static let fileLocksQueue = DispatchQueue(label: "expo-video-cache-file-locks")
20
22
 
21
23
  init(loadingRequest: AVAssetResourceLoadingRequest, dataTask: URLSessionDataTask, dataRequest: AVAssetResourceLoadingDataRequest) {
22
24
  self.loadingRequest = loadingRequest
@@ -30,8 +32,45 @@ class CachableRequest: Equatable, Hashable {
30
32
  }
31
33
 
32
34
  func saveData(to cachedResource: CachedResource) {
33
- // Disabled: Player is now read-only with respect to the cache.
34
- // Do nothing.
35
+ // 1. Check if file is fully cached
36
+ if let mediaInfo = cachedResource.mediaInfo {
37
+ let expectedLength = Int(mediaInfo.expectedContentLength)
38
+ let ranges = mediaInfo.loadedDataRanges
39
+ if ranges.count == 1, ranges[0].0 == 0, ranges[0].1 == expectedLength - 1 {
40
+ // Already fully cached, do not write
41
+ return
42
+ }
43
+ }
44
+
45
+ // 2. Acquire per-file lock (non-blocking)
46
+ let filePath = cachedResource.dataPath
47
+ let lock: NSLock = Self.fileLocksQueue.sync {
48
+ if let l = Self.fileLocks[filePath] { return l }
49
+ let l = NSLock()
50
+ Self.fileLocks[filePath] = l
51
+ return l
52
+ }
53
+ if !lock.try() {
54
+ // Another writer is active, skip this write
55
+ return
56
+ }
57
+
58
+ // 3. Perform async write with final range check inside the task
59
+ let offset = dataOffset
60
+ let length = receivedData.count
61
+ let end = offset + Int64(length) - 1
62
+
63
+ Task.detached(priority: .userInitiated) { [receivedData] in
64
+ // Final check inside the async task to prevent race conditions
65
+ if cachedResource.canRespondWithData(from: offset, to: end) {
66
+ // This range is already cached, skip writing
67
+ lock.unlock()
68
+ return
69
+ }
70
+
71
+ await cachedResource.writeData(data: receivedData, offset: offset)
72
+ lock.unlock()
73
+ }
35
74
  }
36
75
 
37
76
  static func == (lhs: CachableRequest, rhs: CachableRequest) -> Bool {
@@ -10,7 +10,7 @@ import ExpoModulesCore
10
10
  */
11
11
  class CachedResource {
12
12
  private let url: URL
13
- private let dataPath: String
13
+ internal let dataPath: String
14
14
  private let fileHandle: MediaFileHandle
15
15
  private(set) var mediaInfo: MediaInfo?
16
16
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stepincto/expo-video",
3
3
  "title": "Expo Video",
4
- "version": "1.0.1",
4
+ "version": "1.0.4",
5
5
  "originalUpstreamVersion": "2.2.2",
6
6
  "description": "A cross-platform, performant video component for React Native and Expo with Web support",
7
7
  "main": "build/index.js",