diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index ac98ad54e3..854a6f29c6 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -674,6 +674,28 @@ public final class MediaBox { return self.resourceData(id: resource.id, size: size, in: range, mode: mode, notifyAboutIncomplete: notifyAboutIncomplete, attemptSynchronously: attemptSynchronously) } + public func internal_resourceData(id: MediaResourceId, size: Int64, in range: Range) -> (file: ManagedFile, length: Int)? { + let paths = self.storePathsForId(id) + + self.timeBasedCleanup.touch(paths: [ + paths.complete + ]) + + if let file = ManagedFile(queue: nil, path: paths.complete, mode: .read), let completeSize = file.getSize() { + let clippedLowerBound = min(completeSize, max(0, range.lowerBound)) + let clippedUpperBound = min(completeSize, max(0, range.upperBound)) + if clippedLowerBound < clippedUpperBound && (clippedUpperBound - clippedLowerBound) <= 64 * 1024 * 1024 { + let _ = file.seek(position: clippedLowerBound) + return (file, Int(clippedUpperBound - clippedLowerBound)) + } else { + return nil + } + } else { + let tempManager = MediaBoxFileManager(queue: nil) + return MediaBoxPartialFile.internal_extractPartialData(manager: tempManager, path: paths.partial, metaPath: paths.partial + ".meta", range: range) + } + } + public func resourceData(id: MediaResourceId, size: Int64, in range: Range, mode: ResourceDataRangeMode = .complete, notifyAboutIncomplete: Bool = false, attemptSynchronously: Bool = false) -> Signal<(Data, Bool), NoError> { return Signal { subscriber in let disposable = MetaDisposable() diff --git a/submodules/Postbox/Sources/MediaBoxFile.swift b/submodules/Postbox/Sources/MediaBoxFile.swift index 44472bd1ce..8e1d9d6d0c 100644 --- a/submodules/Postbox/Sources/MediaBoxFile.swift +++ b/submodules/Postbox/Sources/MediaBoxFile.swift @@ -92,6 +92,20 @@ final class MediaBoxPartialFile { return fd.readData(count: Int(clippedRange.upperBound - clippedRange.lowerBound)) } + static func internal_extractPartialData(manager: MediaBoxFileManager, path: String, metaPath: String, range: Range) -> (file: ManagedFile, length: Int)? { + guard let fd = ManagedFile(queue: nil, path: path, mode: .read) else { + return nil + } + guard let fileMap = try? MediaBoxFileMap.read(manager: manager, path: metaPath) else { + return nil + } + guard let clippedRange = fileMap.contains(range) else { + return nil + } + let _ = fd.seek(position: Int64(clippedRange.lowerBound)) + return (fd, Int(clippedRange.upperBound - clippedRange.lowerBound)) + } + var storedSize: Int64 { assert(self.queue.isCurrent()) return self.fileMap.sum diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift index 3caa0b44ef..e8d65a508d 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift @@ -248,7 +248,41 @@ private final class HLSServerSource: SharedHLSServer.Source { let mappedRange: Range = Int64(range.lowerBound) ..< Int64(range.upperBound) let queue = postbox.mediaBox.dataQueue - return Signal<(TempBoxFile, Range, Int)?, NoError> { subscriber in + let fetchFromRemote: Signal<(TempBoxFile, Range, Int)?, NoError> = Signal { subscriber in + let partialFile = TempBox.shared.tempFile(fileName: "data") + + if let cachedData = postbox.mediaBox.internal_resourceData(id: file.media.resource.id, size: size, in: Int64(range.lowerBound) ..< Int64(range.upperBound)) { + #if DEBUG + print("Fetched \(quality)p part from cache") + #endif + + let outputFile = ManagedFile(queue: nil, path: partialFile.path, mode: .readwrite) + if let outputFile { + let blockSize = 128 * 1024 + var tempBuffer = Data(count: blockSize) + var blockOffset = 0 + while blockOffset < cachedData.length { + let currentBlockSize = min(cachedData.length - blockOffset, blockSize) + + tempBuffer.withUnsafeMutableBytes { bytes -> Void in + let _ = cachedData.file.read(bytes.baseAddress!, currentBlockSize) + let _ = outputFile.write(bytes.baseAddress!, count: currentBlockSize) + } + + blockOffset += blockSize + } + outputFile._unsafeClose() + subscriber.putNext((partialFile, 0 ..< cachedData.length, Int(size))) + subscriber.putCompletion() + } else { + #if DEBUG + print("Error writing cached file to disk") + #endif + } + + return EmptyDisposable + } + guard let fetchResource = postbox.mediaBox.fetchResource else { return EmptyDisposable } @@ -263,7 +297,6 @@ private final class HLSServerSource: SharedHLSServer.Source { ) let completeFile = TempBox.shared.tempFile(fileName: "data") - let partialFile = TempBox.shared.tempFile(fileName: "data") let metaFile = TempBox.shared.tempFile(fileName: "data") guard let fileContext = MediaBoxFileContextV2Impl( @@ -321,6 +354,8 @@ private final class HLSServerSource: SharedHLSServer.Source { } } |> runOn(queue) + + return fetchFromRemote } } @@ -1102,6 +1137,12 @@ private final class HLSVideoJSContentNode: ASDisplayNode, UniversalVideoContentN self.userLocation = userLocation self.requestedBaseRate = baseRate + #if DEBUG + if let minimizedQualityFile = HLSVideoContent.minimizedHLSQualityFile(file: self.fileReference) { + let _ = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: .video, reference: minimizedQualityFile.resourceReference(minimizedQualityFile.media.resource), range: (0 ..< 5 * 1024 * 1024, .default)).startStandalone() + } + #endif + if var dimensions = fileReference.media.dimensions { if let thumbnail = fileReference.media.previewRepresentations.first { let dimensionsVertical = dimensions.width < dimensions.height @@ -1406,7 +1447,7 @@ private final class HLSVideoJSContentNode: ASDisplayNode, UniversalVideoContentN if !self.initializedStatus { self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: self.requestedBaseRate, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) } - if !self.hasAudioSession { + /*if !self.hasAudioSession { self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in Queue.mainQueue().async { guard let self else { @@ -1428,7 +1469,7 @@ private final class HLSVideoJSContentNode: ASDisplayNode, UniversalVideoContentN } |> runOn(.mainQueue()) })) - } else { + } else*/ do { self.requestPlay() } }