Various improvements

This commit is contained in:
Isaac 2024-11-01 15:14:28 +01:00
parent d7fadef9ef
commit 3371078944
8 changed files with 359 additions and 341 deletions

View File

@ -200,14 +200,10 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
}
}
}
if point.x < activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
activeSide = false
}
} else if point.x > size.width - activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
activeSide = true
}
if point.x < activeEdgeWidth(width: size.width), let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
activeSide = false
} else if point.x > 0.0, let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
activeSide = true
}
if !strongSelf.pagingEnabled {
@ -250,14 +246,10 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
}
}
}
if point.x < activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
activeSide = false
}
} else if point.x > size.width - activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
activeSide = true
}
if point.x < activeEdgeWidth(width: size.width), let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
activeSide = false
} else if point.x > 0.0, let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
activeSide = true
}
}

View File

@ -1336,7 +1336,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private let playbackRatePromise = ValuePromise<Double>()
private let videoQualityPromise = ValuePromise<UniversalVideoContentVideoQuality>()
private var playerStatusValue: MediaPlayerStatus?
private let statusDisposable = MetaDisposable()
private let moreButtonStateDisposable = MetaDisposable()
private let settingsButtonStateDisposable = MetaDisposable()
private let mediaPlaybackStateDisposable = MetaDisposable()
@ -1927,6 +1929,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusDisposable.set((combineLatest(queue: .mainQueue(), videoNode.status, mediaFileStatus)
|> deliverOnMainQueue).start(next: { [weak self] value, fetchStatus in
if let strongSelf = self {
strongSelf.playerStatusValue = value
var initialBuffering = false
var isPlaying = false
var isPaused = true
@ -3598,84 +3602,109 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return
}
var items: [ContextMenuItem] = []
var allFiles: [FileMediaReference] = []
allFiles.append(content.fileReference)
allFiles.append(contentsOf: qualitySet.qualityFiles.values)
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
}, iconPosition: .left, action: { c, _ in
c?.popItems()
})))
let addItem: (Int?, FileMediaReference) -> Void = { quality, qualityFile in
guard let qualityFileSize = qualityFile.media.size else {
let qualitySignals = allFiles.map { file -> Signal<(fileId: MediaId, isCached: Bool), NoError> in
return self.context.account.postbox.mediaBox.resourceStatus(file.media.resource)
|> take(1)
|> map { status -> (fileId: MediaId, isCached: Bool) in
return (file.media.fileId, status == .Local)
}
}
let _ = (combineLatest(queue: .mainQueue(), qualitySignals)
|> deliverOnMainQueue).startStandalone(next: { [weak self, weak c] fileStatuses in
guard let self else {
return
}
let fileSizeString = dataSizeString(qualityFileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData))
let title: String
if let quality {
title = self.presentationData.strings.Gallery_SaveToGallery_Quality("\(quality)").string
} else {
title = self.presentationData.strings.Gallery_SaveToGallery_Original
}
items.append(.action(ContextMenuActionItem(text: title, textLayout: .secondLineWithValue(fileSizeString), icon: { _ in
return nil
}, action: { [weak self] c, _ in
c?.dismiss(result: .default, completion: nil)
guard let self else {
return
}
guard let controller = self.galleryController() else {
return
}
let saveScreen = SaveProgressScreen(context: self.context, content: .progress(self.presentationData.strings.Story_TooltipSaving, 0.0))
controller.present(saveScreen, in: .current)
let stringSaving = self.presentationData.strings.Story_TooltipSaving
let stringSaved = self.presentationData.strings.Story_TooltipSaved
let saveFileReference: AnyMediaReference = qualityFile.abstract
let saveSignal = SaveToCameraRoll.saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: saveFileReference)
let disposable = (saveSignal
|> deliverOnMainQueue).start(next: { [weak saveScreen] progress in
guard let saveScreen else {
return
}
saveScreen.content = .progress(stringSaving, progress)
}, completed: { [weak saveScreen] in
guard let saveScreen else {
return
}
saveScreen.content = .completion(stringSaved)
Queue.mainQueue().after(3.0, { [weak saveScreen] in
saveScreen?.dismiss()
})
})
saveScreen.cancelled = {
disposable.dispose()
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
}, iconPosition: .left, action: { c, _ in
c?.popItems()
})))
}
if self.context.isPremium {
addItem(nil, content.fileReference)
} else {
#if DEBUG
addItem(nil, content.fileReference)
#endif
}
for quality in qualityState.available {
guard let qualityFile = qualitySet.qualityFiles[quality] else {
continue
let addItem: (Int?, FileMediaReference) -> Void = { quality, qualityFile in
guard let qualityFileSize = qualityFile.media.size else {
return
}
var fileSizeString = dataSizeString(qualityFileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData))
let title: String
if let quality {
title = self.presentationData.strings.Gallery_SaveToGallery_Quality("\(quality)").string
} else {
title = self.presentationData.strings.Gallery_SaveToGallery_Original
}
if let statusValue = fileStatuses.first(where: { $0.fileId == qualityFile.media.fileId }), statusValue.isCached {
fileSizeString.append(" • cached")
} else {
fileSizeString.insert(contentsOf: "", at: fileSizeString.startIndex)
}
items.append(.action(ContextMenuActionItem(text: title, textLayout: .secondLineWithValue(fileSizeString), icon: { _ in
return nil
}, action: { [weak self] c, _ in
c?.dismiss(result: .default, completion: nil)
guard let self else {
return
}
guard let controller = self.galleryController() else {
return
}
let saveScreen = SaveProgressScreen(context: self.context, content: .progress(self.presentationData.strings.Story_TooltipSaving, 0.0))
controller.present(saveScreen, in: .current)
let stringSaving = self.presentationData.strings.Story_TooltipSaving
let stringSaved = self.presentationData.strings.Story_TooltipSaved
let saveFileReference: AnyMediaReference = qualityFile.abstract
let saveSignal = SaveToCameraRoll.saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: saveFileReference)
let disposable = (saveSignal
|> deliverOnMainQueue).start(next: { [weak saveScreen] progress in
guard let saveScreen else {
return
}
saveScreen.content = .progress(stringSaving, progress)
}, completed: { [weak saveScreen] in
guard let saveScreen else {
return
}
saveScreen.content = .completion(stringSaved)
Queue.mainQueue().after(3.0, { [weak saveScreen] in
saveScreen?.dismiss()
})
})
saveScreen.cancelled = {
disposable.dispose()
}
})))
}
addItem(quality, qualityFile)
}
c?.pushItems(items: .single(ContextController.Items(content: .list(items))))
if self.context.isPremium {
addItem(nil, content.fileReference)
} else {
#if DEBUG
addItem(nil, content.fileReference)
#endif
}
for quality in qualityState.available {
guard let qualityFile = qualitySet.qualityFiles[quality] else {
continue
}
addItem(quality, qualityFile)
}
c?.pushItems(items: .single(ContextController.Items(content: .list(items))))
})
} else {
c?.dismiss(result: .default, completion: nil)
@ -3984,7 +4013,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
override func hasActiveEdgeAction(edge: ActiveEdge) -> Bool {
if case .right = edge {
return true
if let playerStatusValue = self.playerStatusValue, case .playing = playerStatusValue.status {
return true
} else {
return false
}
} else {
return false
}
@ -3997,13 +4030,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let edge, case .right = edge {
let effectiveRate: Double
if let current = self.activeEdgeRateState {
effectiveRate = min(2.5, current.initialRate + 0.5)
effectiveRate = min(4.0, current.initialRate + 1.0)
self.activeEdgeRateState = (current.initialRate, effectiveRate)
} else {
guard let playbackRate = self.playbackRate else {
return
}
effectiveRate = min(2.5, playbackRate + 0.5)
effectiveRate = min(4.0, playbackRate + 1.0)
self.activeEdgeRateState = (playbackRate, effectiveRate)
}
videoNode.setBaseRate(effectiveRate)
@ -4023,9 +4056,16 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
if let current = self.activeEdgeRateState {
var rateFraction = Double(distance) / 100.0
rateFraction = max(0.0, min(1.0, rateFraction))
let rateDistance = (current.initialRate + 0.5) * (1.0 - rateFraction) + 2.5 * rateFraction
let effectiveRate = max(1.0, min(2.5, rateDistance))
rateFraction = max(-1.0, min(1.0, rateFraction))
let effectiveRate: Double
if rateFraction < 0.0 {
let rateDistance = (current.initialRate + 1.0) * (1.0 - (-rateFraction)) + 1.0 * (-rateFraction)
effectiveRate = max(1.0, min(4.0, rateDistance))
} else {
let rateDistance = (current.initialRate + 1.0) * (1.0 - rateFraction) + 3.0 * rateFraction
effectiveRate = max(1.0, min(4.0, rateDistance))
}
self.activeEdgeRateState = (current.initialRate, effectiveRate)
videoNode.setBaseRate(effectiveRate)

View File

@ -317,6 +317,15 @@ public final class MediaBox {
}
}
public func storeResourceData(_ id: MediaResourceId, range: Range<Int64>, data: Data) {
self.dataQueue.async {
if let (fileContext, dispose) = self.fileContext(for: id) {
fileContext.internalStore(data: data, range: range)
dispose()
}
}
}
public func moveResourceData(_ id: MediaResourceId, fromTempPath: String) {
self.dataQueue.async {
let paths = self.storePathsForId(id)

View File

@ -748,139 +748,3 @@ private final class MediaBoxFileMissingRanges {
return nil
}
}
private enum MediaBoxFileContent {
case complete(String, Int64)
case partial(MediaBoxPartialFile)
}
final class MediaBoxFileContextImpl: MediaBoxFileContext {
private let queue: Queue
private let path: String
private let partialPath: String
private let metaPath: String
private var content: MediaBoxFileContent
private let references = CounterBag()
var isEmpty: Bool {
return self.references.isEmpty
}
init?(queue: Queue, manager: MediaBoxFileManager, storageBox: StorageBox, resourceId: Data, path: String, partialPath: String, metaPath: String) {
assert(queue.isCurrent())
self.queue = queue
self.path = path
self.partialPath = partialPath
self.metaPath = metaPath
var completeImpl: ((Int64) -> Void)?
if let size = fileSize(path) {
self.content = .complete(path, size)
} else if let file = MediaBoxPartialFile(queue: queue, manager: manager, storageBox: storageBox, resourceId: resourceId, path: partialPath, metaPath: metaPath, completePath: path, completed: { size in
completeImpl?(size)
}) {
self.content = .partial(file)
completeImpl = { [weak self] size in
queue.async {
if let strongSelf = self {
strongSelf.content = .complete(path, size)
}
}
}
} else {
return nil
}
}
deinit {
assert(self.queue.isCurrent())
}
func addReference() -> Int {
return self.references.add()
}
func removeReference(_ index: Int) {
self.references.remove(index)
}
func data(range: Range<Int64>, waitUntilAfterInitialFetch: Bool, next: @escaping (MediaResourceData) -> Void) -> Disposable {
switch self.content {
case let .complete(path, size):
var lowerBound = range.lowerBound
if lowerBound < 0 {
lowerBound = 0
}
if lowerBound > size {
lowerBound = size
}
var upperBound = range.upperBound
if upperBound < 0 {
upperBound = 0
}
if upperBound > size {
upperBound = size
}
if upperBound < lowerBound {
upperBound = lowerBound
}
next(MediaResourceData(path: path, offset: lowerBound, size: upperBound - lowerBound, complete: true))
return EmptyDisposable
case let .partial(file):
return file.data(range: range, waitUntilAfterInitialFetch: waitUntilAfterInitialFetch, next: next)
}
}
func fetched(range: Range<Int64>, priority: MediaBoxFetchPriority, fetch: @escaping (Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError>) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>, error: @escaping (MediaResourceDataFetchError) -> Void, completed: @escaping () -> Void) -> Disposable {
switch self.content {
case .complete:
completed()
return EmptyDisposable
case let .partial(file):
return file.fetched(range: range, priority: priority, fetch: fetch, error: error, completed: completed)
}
}
func fetchedFullRange(fetch: @escaping (Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError>) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>, error: @escaping (MediaResourceDataFetchError) -> Void, completed: @escaping () -> Void) -> Disposable {
switch self.content {
case .complete:
return EmptyDisposable
case let .partial(file):
return file.fetchedFullRange(fetch: fetch, error: error, completed: completed)
}
}
func cancelFullRangeFetches() {
switch self.content {
case .complete:
break
case let .partial(file):
file.cancelFullRangeFetches()
}
}
func rangeStatus(next: @escaping (RangeSet<Int64>) -> Void, completed: @escaping () -> Void) -> Disposable {
switch self.content {
case let .complete(_, size):
next(RangeSet<Int64>(0 ..< size))
completed()
return EmptyDisposable
case let .partial(file):
return file.rangeStatus(next: next, completed: completed)
}
}
func status(next: @escaping (MediaResourceStatus) -> Void, completed: @escaping () -> Void, size: Int64?) -> Disposable {
switch self.content {
case .complete:
next(.Local)
return EmptyDisposable
case let .partial(file):
return file.status(next: next, completed: completed, size: size)
}
}
}

View File

@ -15,4 +15,6 @@ protocol MediaBoxFileContext: AnyObject {
func cancelFullRangeFetches()
func rangeStatus(next: @escaping (RangeSet<Int64>) -> Void, completed: @escaping () -> Void) -> Disposable
func status(next: @escaping (MediaResourceStatus) -> Void, completed: @escaping () -> Void, size: Int64?) -> Disposable
func internalStore(data: Data, range: Range<Int64>)
}

View File

@ -296,6 +296,16 @@ public final class MediaBoxFileContextV2Impl: MediaBoxFileContext {
}
}
func internalStore(data: Data, range: Range<Int64>) {
assert(self.queue.isCurrent())
if data.count == Int(range.upperBound - range.lowerBound) {
self.processFetchResult(result: .dataPart(resourceOffset: range.lowerBound, data: data, range: 0 ..< Int64(data.count), complete: false))
} else {
assertionFailure()
}
}
private func updateRequests() {
var rangesByPriority: [MediaBoxFetchPriority: RangeSet<Int64>] = [:]
for (index, rangeRequest) in self.rangeRequests.copyItemsWithIndices() {
@ -795,4 +805,13 @@ public final class MediaBoxFileContextV2Impl: MediaBoxFileContext {
}
}
}
public func internalStore(data: Data, range: Range<Int64>) {
if let _ = fileSize(self.path) {
} else {
self.withPartialState { partialState in
partialState.internalStore(data: data, range: range)
}
}
}
}

View File

@ -1491,15 +1491,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let timestamp = Int(CFAbsoluteTimeGetCurrent())
let minReindexTimestamp = timestamp - 2 * 24 * 60 * 60
if let indexTimestamp = UserDefaults.standard.object(forKey: "TelegramCacheIndexTimestamp") as? NSNumber, indexTimestamp.intValue >= minReindexTimestamp {
#if DEBUG && false
Logger.shared.log("App \(self.episodeId)", "Executing low-impact cache reindex in foreground")
let _ = self.runCacheReindexTasks(lowImpact: true, completion: {
Logger.shared.log("App \(self.episodeId)", "Executing low-impact cache reindex in foreground — done1")
})
#endif
if let indexTimestamp = UserDefaults.standard.object(forKey: "TelegramCacheIndexTimestamp_v2") as? NSNumber, indexTimestamp.intValue >= minReindexTimestamp {
} else {
UserDefaults.standard.set(timestamp as NSNumber, forKey: "TelegramCacheIndexTimestamp")
UserDefaults.standard.set(timestamp as NSNumber, forKey: "TelegramCacheIndexTimestamp_v2")
Logger.shared.log("App \(self.episodeId)", "Executing low-impact cache reindex in foreground")
let _ = self.runCacheReindexTasks(lowImpact: true, completion: {

View File

@ -146,7 +146,7 @@ final class HLSJSServerSource: SharedHLSServer.Source {
guard let (quality, file) = self.qualityFiles.first(where: { $0.value.media.fileId.id == id }) else {
return .single(nil)
}
let _ = quality
guard let size = file.media.size else {
return .single(nil)
}
@ -154,115 +154,174 @@ final class HLSJSServerSource: SharedHLSServer.Source {
let postbox = self.postbox
let userLocation = self.userLocation
let playlistPreloadRange = self.playlistData(quality: quality)
|> mapToSignal { playlistString -> Signal<Range<Int64>?, NoError> in
var durations: [Int] = []
var byteRanges: [Range<Int>] = []
let extinfRegex = try! NSRegularExpression(pattern: "EXTINF:(\\d+)", options: [])
let byteRangeRegex = try! NSRegularExpression(pattern: "EXT-X-BYTERANGE:(\\d+)@(\\d+)", options: [])
let extinfResults = extinfRegex.matches(in: playlistString, range: NSRange(playlistString.startIndex..., in: playlistString))
for result in extinfResults {
if let durationRange = Range(result.range(at: 1), in: playlistString) {
if let duration = Int(String(playlistString[durationRange])) {
durations.append(duration)
}
}
}
let byteRangeResults = byteRangeRegex.matches(in: playlistString, range: NSRange(playlistString.startIndex..., in: playlistString))
for result in byteRangeResults {
if let lengthRange = Range(result.range(at: 1), in: playlistString), let upperBoundRange = Range(result.range(at: 2), in: playlistString) {
if let length = Int(String(playlistString[lengthRange])), let lowerBound = Int(String(playlistString[upperBoundRange])) {
byteRanges.append(lowerBound ..< (lowerBound + length))
}
}
}
let prefixSeconds = 10
var rangeUpperBound: Int64 = 0
if durations.count == byteRanges.count {
var remainingSeconds = prefixSeconds
for i in 0 ..< durations.count {
if remainingSeconds <= 0 {
break
}
let duration = durations[i]
let byteRange = byteRanges[i]
remainingSeconds -= duration
rangeUpperBound = max(rangeUpperBound, Int64(byteRange.upperBound))
}
}
if rangeUpperBound != 0 {
return .single(0 ..< rangeUpperBound)
} else {
return .single(nil)
}
}
let mappedRange: Range<Int64> = Int64(range.lowerBound) ..< Int64(range.upperBound)
let queue = postbox.mediaBox.dataQueue
let fetchFromRemote: Signal<(TempBoxFile, Range<Int>, 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)) {
let fetchFromRemote: Signal<(TempBoxFile, Range<Int>, Int)?, NoError> = playlistPreloadRange
|> mapToSignal { preloadRange -> Signal<(TempBoxFile, Range<Int>, Int)?, NoError> in
return 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
}
let location = MediaResourceStorageLocation(userLocation: userLocation, reference: file.resourceReference(file.media.resource))
let params = MediaResourceFetchParameters(
tag: TelegramMediaResourceFetchTag(statsCategory: .video, userContentType: .video),
info: TelegramCloudMediaResourceFetchInfo(reference: file.resourceReference(file.media.resource), preferBackgroundReferenceRevalidation: true, continueInBackground: true),
location: location,
contentType: .video,
isRandomAccessAllowed: true
)
let completeFile = TempBox.shared.tempFile(fileName: "data")
let metaFile = TempBox.shared.tempFile(fileName: "data")
guard let fileContext = MediaBoxFileContextV2Impl(
queue: queue,
manager: postbox.mediaBox.dataFileManager,
storageBox: nil,
resourceId: file.media.resource.id.stringRepresentation.data(using: .utf8)!,
path: completeFile.path,
partialPath: partialFile.path,
metaPath: metaFile.path
) else {
return EmptyDisposable
}
let fetchDisposable = fileContext.fetched(
range: mappedRange,
priority: .default,
fetch: { intervals in
return fetchResource(file.media.resource, intervals, params)
},
error: { _ in
},
completed: {
}
)
#if DEBUG
print("Fetched \(quality)p part from cache")
let startTime = CFAbsoluteTimeGetCurrent()
#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)
let dataDisposable = fileContext.data(
range: mappedRange,
waitUntilAfterInitialFetch: true,
next: { result in
if result.complete {
#if DEBUG
let fetchTime = CFAbsoluteTimeGetCurrent() - startTime
print("Fetching \(quality)p part took \(fetchTime * 1000.0) ms")
#endif
if let preloadRange, Int(preloadRange.lowerBound) <= range.upperBound && Int(preloadRange.upperBound) >= range.lowerBound {
if let data = try? Data(contentsOf: URL(fileURLWithPath: partialFile.path), options: .alwaysMapped) {
let subData = data.subdata(in: Int(result.offset) ..< Int(result.offset + result.size))
postbox.mediaBox.storeResourceData(file.media.resource.id, range: Int64(range.lowerBound) ..< Int64(range.upperBound), data: subData)
}
}
subscriber.putNext((partialFile, Int(result.offset) ..< Int(result.offset + result.size), Int(size)))
subscriber.putCompletion()
}
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
}
let location = MediaResourceStorageLocation(userLocation: userLocation, reference: file.resourceReference(file.media.resource))
let params = MediaResourceFetchParameters(
tag: TelegramMediaResourceFetchTag(statsCategory: .video, userContentType: .video),
info: TelegramCloudMediaResourceFetchInfo(reference: file.resourceReference(file.media.resource), preferBackgroundReferenceRevalidation: true, continueInBackground: true),
location: location,
contentType: .video,
isRandomAccessAllowed: true
)
let completeFile = TempBox.shared.tempFile(fileName: "data")
let metaFile = TempBox.shared.tempFile(fileName: "data")
guard let fileContext = MediaBoxFileContextV2Impl(
queue: queue,
manager: postbox.mediaBox.dataFileManager,
storageBox: nil,
resourceId: file.media.resource.id.stringRepresentation.data(using: .utf8)!,
path: completeFile.path,
partialPath: partialFile.path,
metaPath: metaFile.path
) else {
return EmptyDisposable
}
let fetchDisposable = fileContext.fetched(
range: mappedRange,
priority: .default,
fetch: { intervals in
return fetchResource(file.media.resource, intervals, params)
},
error: { _ in
},
completed: {
}
)
#if DEBUG
let startTime = CFAbsoluteTimeGetCurrent()
#endif
let dataDisposable = fileContext.data(
range: mappedRange,
waitUntilAfterInitialFetch: true,
next: { result in
if result.complete {
#if DEBUG
let fetchTime = CFAbsoluteTimeGetCurrent() - startTime
print("Fetching \(quality)p part took \(fetchTime * 1000.0) ms")
#endif
subscriber.putNext((partialFile, Int(result.offset) ..< Int(result.offset + result.size), Int(size)))
subscriber.putCompletion()
return ActionDisposable {
queue.async {
fetchDisposable.dispose()
dataDisposable.dispose()
fileContext.cancelFullRangeFetches()
TempBox.shared.dispose(completeFile)
TempBox.shared.dispose(metaFile)
}
}
)
return ActionDisposable {
queue.async {
fetchDisposable.dispose()
dataDisposable.dispose()
fileContext.cancelFullRangeFetches()
TempBox.shared.dispose(completeFile)
TempBox.shared.dispose(metaFile)
}
}
|> runOn(queue)
}
|> runOn(queue)
return fetchFromRemote
}
@ -1287,8 +1346,15 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
if !self.hasRequestedPlayerLoad {
if !self.playerAvailableLevels.isEmpty {
var selectedLevelIndex: Int?
if let minimizedQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file {
if let dimensions = minimizedQualityFile.media.dimensions {
if let qualityFiles = HLSQualitySet(baseFile: self.fileReference)?.qualityFiles.values, let maxQualityFile = qualityFiles.max(by: { lhs, rhs in
if let lhsDimensions = lhs.media.dimensions, let rhsDimensions = rhs.media.dimensions {
return lhsDimensions.width < rhsDimensions.width
} else {
return lhs.media.fileId.id < rhs.media.fileId.id
}
}), let dimensions = maxQualityFile.media.dimensions {
if self.postbox.mediaBox.completedResourcePath(maxQualityFile.media.resource) != nil {
for (index, level) in self.playerAvailableLevels {
if level.height == Int(dimensions.height) {
selectedLevelIndex = index
@ -1297,6 +1363,19 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
}
}
}
if selectedLevelIndex == nil {
if let minimizedQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file {
if let dimensions = minimizedQualityFile.media.dimensions {
for (index, level) in self.playerAvailableLevels {
if level.height == Int(dimensions.height) {
selectedLevelIndex = index
break
}
}
}
}
}
if selectedLevelIndex == nil {
selectedLevelIndex = self.playerAvailableLevels.sorted(by: { $0.value.height > $1.value.height }).first?.key
}
@ -1612,10 +1691,29 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
}
}
guard let playerCurrentLevelIndex = self.playerCurrentLevelIndex else {
return nil
let currentLevelIndex: Int
if let playerCurrentLevelIndex = self.playerCurrentLevelIndex {
currentLevelIndex = playerCurrentLevelIndex
} else {
if let minQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file, let dimensions = minQualityFile.media.dimensions {
var foundIndex: Int?
for (index, level) in self.playerAvailableLevels {
if level.width == Int(dimensions.width) && level.height == Int(dimensions.height) {
foundIndex = index
break
}
}
if let foundIndex {
currentLevelIndex = foundIndex
} else {
return nil
}
} else {
return nil
}
}
guard let currentLevel = self.playerAvailableLevels[playerCurrentLevelIndex] else {
guard let currentLevel = self.playerAvailableLevels[currentLevelIndex] else {
return nil
}