Refactor RangeSet for 32-bit support

This commit is contained in:
Ali 2022-05-11 23:41:57 +04:00
parent 3f6dabad1e
commit fc89536b5c
21 changed files with 134 additions and 80 deletions

View File

@ -20,7 +20,8 @@ swift_library(
"//submodules/Postbox:Postbox", "//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore", "//submodules/TelegramCore:TelegramCore",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources", "//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
"//submodules/MeshAnimationCache:MeshAnimationCache" "//submodules/MeshAnimationCache:MeshAnimationCache",
"//submodules/Utils/RangeSet:RangeSet",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -6,6 +6,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import TelegramAudio import TelegramAudio
import UniversalMediaPlayer import UniversalMediaPlayer
import RangeSet
public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId {
case peer(PeerId) case peer(PeerId)
@ -200,7 +201,7 @@ public protocol UniversalVideoManager: AnyObject {
func addPlaybackCompleted(id: AnyHashable, _ f: @escaping () -> Void) -> Int func addPlaybackCompleted(id: AnyHashable, _ f: @escaping () -> Void) -> Int
func removePlaybackCompleted(id: AnyHashable, index: Int) func removePlaybackCompleted(id: AnyHashable, index: Int)
func statusSignal(content: UniversalVideoContent) -> Signal<MediaPlayerStatus?, NoError> func statusSignal(content: UniversalVideoContent) -> Signal<MediaPlayerStatus?, NoError>
func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(IndexSet, Int64)?, NoError> func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(RangeSet<Int64>, Int64)?, NoError>
} }
public enum AudioRecordingState: Equatable { public enum AudioRecordingState: Equatable {

View File

@ -8,11 +8,12 @@ import Display
import TelegramAudio import TelegramAudio
import UniversalMediaPlayer import UniversalMediaPlayer
import AVFoundation import AVFoundation
import RangeSet
public protocol UniversalVideoContentNode: AnyObject { public protocol UniversalVideoContentNode: AnyObject {
var ready: Signal<Void, NoError> { get } var ready: Signal<Void, NoError> { get }
var status: Signal<MediaPlayerStatus, NoError> { get } var status: Signal<MediaPlayerStatus, NoError> { get }
var bufferingStatus: Signal<(IndexSet, Int64)?, NoError> { get } var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> { get }
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
@ -105,8 +106,8 @@ public final class UniversalVideoNode: ASDisplayNode {
return self._status.get() return self._status.get()
} }
private let _bufferingStatus = Promise<(IndexSet, Int64)?>() private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>()
public var bufferingStatus: Signal<(IndexSet, Int64)?, NoError> { public var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> {
return self._bufferingStatus.get() return self._bufferingStatus.get()
} }

View File

@ -40,6 +40,7 @@ swift_library(
"//submodules/UndoUI:UndoUI", "//submodules/UndoUI:UndoUI",
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode", "//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
"//submodules/TranslateUI:TranslateUI", "//submodules/TranslateUI:TranslateUI",
"//submodules/Utils/RangeSet:RangeSet",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -7,6 +7,7 @@ import Postbox
import Display import Display
import UniversalMediaPlayer import UniversalMediaPlayer
import TelegramPresentationData import TelegramPresentationData
import RangeSet
private let textFont = Font.with(size: 13.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) private let textFont = Font.with(size: 13.0, design: .regular, weight: .regular, traits: [.monospacedNumbers])
@ -255,7 +256,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
})) }))
} }
func setBufferingStatusSignal(_ status: Signal<(IndexSet, Int64)?, NoError>?) { func setBufferingStatusSignal(_ status: Signal<(RangeSet<Int64>, Int64)?, NoError>?) {
self.scrubberNode.bufferingStatus = status self.scrubberNode.bufferingStatus = status
} }

View File

@ -19,6 +19,7 @@ swift_library(
"//submodules/FFMpegBinding:FFMpegBinding", "//submodules/FFMpegBinding:FFMpegBinding",
"//submodules/RingBuffer:RingBuffer", "//submodules/RingBuffer:RingBuffer",
"//submodules/YuvConversion:YuvConversion", "//submodules/YuvConversion:YuvConversion",
"//submodules/Utils/RangeSet:RangeSet",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -2,6 +2,7 @@ import Foundation
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import RangeSet
public enum MediaPlayerScrubbingNodeCap { public enum MediaPlayerScrubbingNodeCap {
case square case square
@ -218,7 +219,7 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode {
private let containerNode: ASDisplayNode private let containerNode: ASDisplayNode
private let foregroundNode: ASImageNode private let foregroundNode: ASImageNode
private var ranges: (IndexSet, Int64)? private var ranges: (RangeSet<Int64>, Int64)?
init(color: UIColor, lineCap: MediaPlayerScrubbingNodeCap, lineHeight: CGFloat) { init(color: UIColor, lineCap: MediaPlayerScrubbingNodeCap, lineHeight: CGFloat) {
self.color = color self.color = color
@ -239,7 +240,7 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode {
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
} }
func updateStatus(_ ranges: IndexSet, _ size: Int64) { func updateStatus(_ ranges: RangeSet<Int64>, _ size: Int64) {
self.ranges = (ranges, size) self.ranges = (ranges, size)
if !self.bounds.width.isZero { if !self.bounds.width.isZero {
self.updateLayout(size: self.bounds.size, transition: .animated(duration: 0.15, curve: .easeInOut)) self.updateLayout(size: self.bounds.size, transition: .animated(duration: 0.15, curve: .easeInOut))
@ -249,7 +250,7 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode {
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) transition.updateFrame(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
if let ranges = self.ranges, !ranges.0.isEmpty, ranges.1 != 0 { if let ranges = self.ranges, !ranges.0.isEmpty, ranges.1 != 0 {
for range in ranges.0.rangeView { for range in ranges.0.ranges {
let rangeWidth = min(size.width, (CGFloat(range.count) / CGFloat(ranges.1)) * size.width) let rangeWidth = min(size.width, (CGFloat(range.count) / CGFloat(ranges.1)) * size.width)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: rangeWidth, height: size.height))) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: rangeWidth, height: size.height)))
transition.updateAlpha(node: self.foregroundNode, alpha: abs(size.width - rangeWidth) < 1.0 ? 0.0 : 1.0) transition.updateAlpha(node: self.foregroundNode, alpha: abs(size.width - rangeWidth) < 1.0 ? 0.0 : 1.0)
@ -358,9 +359,9 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
} }
private var bufferingStatusDisposable: Disposable? private var bufferingStatusDisposable: Disposable?
private var bufferingStatusValuePromise = Promise<(IndexSet, Int64)?>() private var bufferingStatusValuePromise = Promise<(RangeSet<Int64>, Int64)?>()
public var bufferingStatus: Signal<(IndexSet, Int64)?, NoError>? { public var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError>? {
didSet { didSet {
if let bufferingStatus = self.bufferingStatus { if let bufferingStatus = self.bufferingStatus {
self.bufferingStatusValuePromise.set(bufferingStatus) self.bufferingStatusValuePromise.set(bufferingStatus)

View File

@ -25,6 +25,7 @@ swift_library(
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources", "//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
"//submodules/Svg:Svg", "//submodules/Svg:Svg",
"//submodules/Utils/RangeSet:RangeSet",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -18,6 +18,7 @@ import ImageTransparency
import AppBundle import AppBundle
import MusicAlbumArtResources import MusicAlbumArtResources
import Svg import Svg
import RangeSet
private enum ResourceFileData { private enum ResourceFileData {
case data(Data) case data(Data)
@ -1671,7 +1672,7 @@ public func chatMessagePhotoStatus(context: AccountContext, messageId: MessageId
context.account.postbox.mediaBox.resourceRangesStatus(largestRepresentation.resource) context.account.postbox.mediaBox.resourceRangesStatus(largestRepresentation.resource)
) )
|> map { status, rangeStatus -> MediaResourceStatus in |> map { status, rangeStatus -> MediaResourceStatus in
if rangeStatus.contains(integersIn: Int(range.lowerBound) ..< Int(range.upperBound)) { if rangeStatus.isSuperset(of: RangeSet<Int64>(range)) {
return .Local return .Local
} }

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import SwiftSignalKit import SwiftSignalKit
import ManagedFile import ManagedFile
import RangeSet
private final class ResourceStatusContext { private final class ResourceStatusContext {
var status: MediaResourceStatus? var status: MediaResourceStatus?
@ -685,7 +686,7 @@ public final class MediaBox {
} }
} }
public func resourceRangesStatus(_ resource: MediaResource) -> Signal<IndexSet, NoError> { public func resourceRangesStatus(_ resource: MediaResource) -> Signal<RangeSet<Int64>, NoError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()

View File

@ -2,16 +2,17 @@ import Foundation
import SwiftSignalKit import SwiftSignalKit
import Crc32 import Crc32
import ManagedFile import ManagedFile
import RangeSet
private final class MediaBoxFileMap { private final class MediaBoxFileMap {
fileprivate(set) var sum: Int64 fileprivate(set) var sum: Int64
private(set) var ranges: IndexSet private(set) var ranges: RangeSet<Int64>
private(set) var truncationSize: Int64? private(set) var truncationSize: Int64?
private(set) var progress: Float? private(set) var progress: Float?
init() { init() {
self.sum = 0 self.sum = 0
self.ranges = IndexSet() self.ranges = RangeSet<Int64>()
self.truncationSize = nil self.truncationSize = nil
self.progress = nil self.progress = nil
} }
@ -34,7 +35,7 @@ private final class MediaBoxFileMap {
var count: Int32 = 0 var count: Int32 = 0
var sum: Int64 = 0 var sum: Int64 = 0
var ranges: IndexSet = IndexSet() var ranges = RangeSet<Int64>()
guard fd.read(&count, 4) == 4 else { guard fd.read(&count, 4) == 4 else {
return nil return nil
@ -74,7 +75,7 @@ private final class MediaBoxFileMap {
memcpy(&intervalLength, bytes.advanced(by: offset + 8), 8) memcpy(&intervalLength, bytes.advanced(by: offset + 8), 8)
offset += 8 * 2 offset += 8 * 2
ranges.insert(integersIn: Int(intervalOffset) ..< Int(intervalOffset + intervalLength)) ranges.insert(contentsOf: intervalOffset ..< (intervalOffset + intervalLength))
sum += intervalLength sum += intervalLength
} }
@ -95,7 +96,7 @@ private final class MediaBoxFileMap {
let crc: UInt32 = firstUInt32 let crc: UInt32 = firstUInt32
var count: Int32 = 0 var count: Int32 = 0
var sum: Int32 = 0 var sum: Int32 = 0
var ranges: IndexSet = IndexSet() var ranges = RangeSet<Int64>()
guard fd.read(&count, 4) == 4 else { guard fd.read(&count, 4) == 4 else {
return nil return nil
@ -135,7 +136,7 @@ private final class MediaBoxFileMap {
memcpy(&intervalLength, bytes.advanced(by: offset + 4), 4) memcpy(&intervalLength, bytes.advanced(by: offset + 4), 4)
offset += 8 offset += 8
ranges.insert(integersIn: Int(intervalOffset) ..< Int(intervalOffset + intervalLength)) ranges.insert(contentsOf: Int64(intervalOffset) ..< Int64(intervalOffset + intervalLength))
sum += intervalLength sum += intervalLength
} }
@ -164,7 +165,7 @@ private final class MediaBoxFileMap {
var zero: Int32 = 0 var zero: Int32 = 0
buffer.write(&zero, offset: 0, length: 4) buffer.write(&zero, offset: 0, length: 4)
let rangeView = self.ranges.rangeView let rangeView = self.ranges.ranges
var count: Int32 = Int32(rangeView.count) var count: Int32 = Int32(rangeView.count)
buffer.write(&count, offset: 0, length: 4) buffer.write(&count, offset: 0, length: 4)
@ -172,8 +173,8 @@ private final class MediaBoxFileMap {
buffer.write(&truncationSizeValue, offset: 0, length: 8) buffer.write(&truncationSizeValue, offset: 0, length: 8)
for range in rangeView { for range in rangeView {
var intervalOffset = Int32(range.lowerBound) var intervalOffset = range.lowerBound
var intervalLength = Int32(range.count) var intervalLength = range.upperBound - range.lowerBound
buffer.write(&intervalOffset, offset: 0, length: 8) buffer.write(&intervalOffset, offset: 0, length: 8)
buffer.write(&intervalLength, offset: 0, length: 8) buffer.write(&intervalLength, offset: 0, length: 8)
} }
@ -184,10 +185,13 @@ private final class MediaBoxFileMap {
} }
fileprivate func fill(_ range: Range<Int64>) { fileprivate func fill(_ range: Range<Int64>) {
let intRange: Range<Int> = Int(range.lowerBound) ..< Int(range.upperBound) var previousCount: Int64 = 0
let previousCount = self.ranges.count(in: intRange) for intersectionRange in self.ranges.intersection(RangeSet<Int64>(range)).ranges {
self.ranges.insert(integersIn: intRange) previousCount += intersectionRange.upperBound - intersectionRange.lowerBound
self.sum += Int64(range.count - previousCount) }
self.ranges.insert(contentsOf: range)
self.sum += (range.upperBound - range.lowerBound) - previousCount
} }
fileprivate func truncate(_ size: Int64) { fileprivate func truncate(_ size: Int64) {
@ -199,21 +203,23 @@ private final class MediaBoxFileMap {
fileprivate func reset() { fileprivate func reset() {
self.truncationSize = nil self.truncationSize = nil
self.ranges.removeAll() self.ranges = RangeSet<Int64>()
self.sum = 0 self.sum = 0
self.progress = nil self.progress = nil
} }
fileprivate func contains(_ range: Range<Int64>) -> Range<Int64>? { fileprivate func contains(_ range: Range<Int64>) -> Range<Int64>? {
let maxValue: Int let maxValue: Int64
if let truncationSize = self.truncationSize { if let truncationSize = self.truncationSize {
maxValue = Int(truncationSize) maxValue = truncationSize
} else { } else {
maxValue = Int.max maxValue = Int64.max
} }
let intRange: Range<Int> = Int(range.lowerBound) ..< min(maxValue, Int(range.upperBound)) let clippedRange: Range<Int64> = range.lowerBound ..< min(maxValue, range.upperBound)
if self.ranges.contains(integersIn: intRange) { let clippedRangeSet = RangeSet<Int64>(clippedRange)
return Int64(intRange.lowerBound) ..< Int64(intRange.upperBound)
if self.ranges.isSuperset(of: clippedRangeSet) {
return clippedRange
} else { } else {
return nil return nil
} }
@ -243,7 +249,7 @@ final class MediaBoxPartialFile {
fileprivate let fileMap: MediaBoxFileMap fileprivate let fileMap: MediaBoxFileMap
private var dataRequests = Bag<MediaBoxPartialFileDataRequest>() private var dataRequests = Bag<MediaBoxPartialFileDataRequest>()
private let missingRanges: MediaBoxFileMissingRanges private let missingRanges: MediaBoxFileMissingRanges
private let rangeStatusRequests = Bag<((IndexSet) -> Void, () -> Void)>() private let rangeStatusRequests = Bag<((RangeSet<Int64>) -> Void, () -> Void)>()
private let statusRequests = Bag<((MediaResourceStatus) -> Void, Int64?)>() private let statusRequests = Bag<((MediaResourceStatus) -> Void, Int64?)>()
private let fullRangeRequests = Bag<Disposable>() private let fullRangeRequests = Bag<Disposable>()
@ -263,7 +269,7 @@ final class MediaBoxPartialFile {
self.fd = fd self.fd = fd
if let fileMap = MediaBoxFileMap(fd: self.metadataFd) { if let fileMap = MediaBoxFileMap(fd: self.metadataFd) {
if !fileMap.ranges.isEmpty { if !fileMap.ranges.isEmpty {
let upperBound = fileMap.ranges[fileMap.ranges.endIndex] let upperBound = fileMap.ranges.ranges.last!.upperBound
if let actualSize = fileSize(path, useTotalFileAllocatedSize: false) { if let actualSize = fileSize(path, useTotalFileAllocatedSize: false) {
if upperBound > actualSize { if upperBound > actualSize {
self.fileMap = MediaBoxFileMap() self.fileMap = MediaBoxFileMap()
@ -460,8 +466,8 @@ final class MediaBoxPartialFile {
assertionFailure() assertionFailure()
removeIndices.append((index, request)) removeIndices.append((index, request))
} else { } else {
let intRange: Range<Int> = Int(request.range.lowerBound) ..< Int(min(maxValue, request.range.upperBound)) let intRange: Range<Int64> = request.range.lowerBound ..< min(maxValue, request.range.upperBound)
if self.fileMap.ranges.contains(integersIn: intRange) { if self.fileMap.ranges.isSuperset(of: RangeSet<Int64>(intRange)) {
removeIndices.append((index, request)) removeIndices.append((index, request))
} }
} }
@ -650,7 +656,7 @@ final class MediaBoxPartialFile {
} }
} }
func rangeStatus(next: @escaping (IndexSet) -> Void, completed: @escaping () -> Void) -> Disposable { func rangeStatus(next: @escaping (RangeSet<Int64>) -> Void, completed: @escaping () -> Void) -> Disposable {
assert(self.queue.isCurrent()) assert(self.queue.isCurrent())
next(self.fileMap.ranges) next(self.fileMap.ranges)
@ -758,7 +764,7 @@ final class MediaBoxPartialFile {
strongSelf.write(offset: resourceOffset, data: data, dataRange: range) strongSelf.write(offset: resourceOffset, data: data, dataRange: range)
} }
if complete { if complete {
if let maxOffset = strongSelf.fileMap.ranges.rangeView.reversed().first?.upperBound { if let maxOffset = strongSelf.fileMap.ranges.ranges.reversed().first?.upperBound {
let maxValue = max(resourceOffset + Int64(range.count), Int64(maxOffset)) let maxValue = max(resourceOffset + Int64(range.count), Int64(maxOffset))
strongSelf.truncate(maxValue) strongSelf.truncate(maxValue)
} }
@ -807,15 +813,14 @@ final class MediaBoxPartialFile {
private final class MediaBoxFileMissingRange { private final class MediaBoxFileMissingRange {
var range: Range<Int64> var range: Range<Int64>
let priority: MediaBoxFetchPriority let priority: MediaBoxFetchPriority
var remainingRanges: IndexSet var remainingRanges: RangeSet<Int64>
let error: (MediaResourceDataFetchError) -> Void let error: (MediaResourceDataFetchError) -> Void
let completion: () -> Void let completion: () -> Void
init(range: Range<Int64>, priority: MediaBoxFetchPriority, error: @escaping (MediaResourceDataFetchError) -> Void, completion: @escaping () -> Void) { init(range: Range<Int64>, priority: MediaBoxFetchPriority, error: @escaping (MediaResourceDataFetchError) -> Void, completion: @escaping () -> Void) {
self.range = range self.range = range
self.priority = priority self.priority = priority
let intRange: Range<Int> = Int(range.lowerBound) ..< Int(range.upperBound) self.remainingRanges = RangeSet<Int64>(range)
self.remainingRanges = IndexSet(integersIn: intRange)
self.error = error self.error = error
self.completion = completion self.completion = completion
} }
@ -824,8 +829,8 @@ private final class MediaBoxFileMissingRange {
private final class MediaBoxFileMissingRanges { private final class MediaBoxFileMissingRanges {
private var requestedRanges = Bag<MediaBoxFileMissingRange>() private var requestedRanges = Bag<MediaBoxFileMissingRange>()
private var missingRangesFlattened = IndexSet() private var missingRangesFlattened = RangeSet<Int64>()
private var missingRangesByPriority: [MediaBoxFetchPriority: IndexSet] = [:] private var missingRangesByPriority: [MediaBoxFetchPriority: RangeSet<Int64>] = [:]
func clear() -> [((MediaResourceDataFetchError) -> Void, () -> Void)] { func clear() -> [((MediaResourceDataFetchError) -> Void, () -> Void)] {
let errorsAndCompletions = self.requestedRanges.copyItems().map({ ($0.error, $0.completion) }) let errorsAndCompletions = self.requestedRanges.copyItems().map({ ($0.error, $0.completion) })
@ -838,14 +843,14 @@ private final class MediaBoxFileMissingRanges {
} }
private func missingRequestedIntervals() -> [(Range<Int64>, MediaBoxFetchPriority)] { private func missingRequestedIntervals() -> [(Range<Int64>, MediaBoxFetchPriority)] {
var intervalsByPriority: [MediaBoxFetchPriority: IndexSet] = [:] var intervalsByPriority: [MediaBoxFetchPriority: RangeSet<Int64>] = [:]
var remainingIntervals = IndexSet() var remainingIntervals = RangeSet<Int64>()
for item in self.requestedRanges.copyItems() { for item in self.requestedRanges.copyItems() {
var requestedInterval = IndexSet(integersIn: Int(item.range.lowerBound) ..< Int(item.range.upperBound)) var requestedInterval = RangeSet<Int64>(item.range)
requestedInterval.formIntersection(self.missingRangesFlattened) requestedInterval.formIntersection(self.missingRangesFlattened)
if !requestedInterval.isEmpty { if !requestedInterval.isEmpty {
if intervalsByPriority[item.priority] == nil { if intervalsByPriority[item.priority] == nil {
intervalsByPriority[item.priority] = IndexSet() intervalsByPriority[item.priority] = RangeSet<Int64>()
} }
intervalsByPriority[item.priority]?.formUnion(requestedInterval) intervalsByPriority[item.priority]?.formUnion(requestedInterval)
remainingIntervals.formUnion(requestedInterval) remainingIntervals.formUnion(requestedInterval)
@ -857,9 +862,9 @@ private final class MediaBoxFileMissingRanges {
for priority in intervalsByPriority.keys.sorted(by: { $0.rawValue > $1.rawValue }) { for priority in intervalsByPriority.keys.sorted(by: { $0.rawValue > $1.rawValue }) {
let currentIntervals = intervalsByPriority[priority]!.intersection(remainingIntervals) let currentIntervals = intervalsByPriority[priority]!.intersection(remainingIntervals)
remainingIntervals.subtract(currentIntervals) remainingIntervals.subtract(currentIntervals)
for range in currentIntervals.rangeView { for range in currentIntervals.ranges {
if !range.isEmpty { if !range.isEmpty {
result.append((Int64(range.lowerBound) ..< Int64(range.upperBound), priority)) result.append((range, priority))
} }
} }
} }
@ -868,17 +873,16 @@ private final class MediaBoxFileMissingRanges {
} }
func fill(_ range: Range<Int64>) -> ([(Range<Int64>, MediaBoxFetchPriority)], [() -> Void])? { func fill(_ range: Range<Int64>) -> ([(Range<Int64>, MediaBoxFetchPriority)], [() -> Void])? {
let intRange: Range<Int> = Int(range.lowerBound) ..< Int(range.upperBound) if self.missingRangesFlattened.intersects(range) {
if self.missingRangesFlattened.intersects(integersIn: intRange) { self.missingRangesFlattened.remove(contentsOf: range)
self.missingRangesFlattened.remove(integersIn: intRange)
for priority in self.missingRangesByPriority.keys { for priority in self.missingRangesByPriority.keys {
self.missingRangesByPriority[priority]!.remove(integersIn: intRange) self.missingRangesByPriority[priority]!.remove(contentsOf: range)
} }
var completions: [() -> Void] = [] var completions: [() -> Void] = []
for (index, item) in self.requestedRanges.copyItemsWithIndices() { for (index, item) in self.requestedRanges.copyItemsWithIndices() {
if item.range.overlaps(range) { if item.range.overlaps(range) {
item.remainingRanges.remove(integersIn: intRange) item.remainingRanges.remove(contentsOf: range)
if item.remainingRanges.isEmpty { if item.remainingRanges.isEmpty {
self.requestedRanges.remove(index) self.requestedRanges.remove(index)
completions.append(item.completion) completions.append(item.completion)
@ -904,15 +908,15 @@ private final class MediaBoxFileMissingRanges {
} }
private func update(fileMap: MediaBoxFileMap) -> [(Range<Int64>, MediaBoxFetchPriority)]? { private func update(fileMap: MediaBoxFileMap) -> [(Range<Int64>, MediaBoxFetchPriority)]? {
var byPriority: [MediaBoxFetchPriority: IndexSet] = [:] var byPriority: [MediaBoxFetchPriority: RangeSet<Int64>] = [:]
var flattened = IndexSet() var flattened = RangeSet<Int64>()
for item in self.requestedRanges.copyItems() { for item in self.requestedRanges.copyItems() {
let intRange: Range<Int> = Int(item.range.lowerBound) ..< Int(item.range.upperBound) let intRange: Range<Int64> = item.range
if byPriority[item.priority] == nil { if byPriority[item.priority] == nil {
byPriority[item.priority] = IndexSet() byPriority[item.priority] = RangeSet<Int64>()
} }
byPriority[item.priority]!.insert(integersIn: intRange) byPriority[item.priority]!.insert(contentsOf: intRange)
flattened.insert(integersIn: intRange) flattened.insert(contentsOf: intRange)
} }
for priority in byPriority.keys { for priority in byPriority.keys {
byPriority[priority]!.subtract(fileMap.ranges) byPriority[priority]!.subtract(fileMap.ranges)
@ -1041,10 +1045,10 @@ final class MediaBoxFileContext {
} }
} }
func rangeStatus(next: @escaping (IndexSet) -> Void, completed: @escaping () -> Void) -> Disposable { func rangeStatus(next: @escaping (RangeSet<Int64>) -> Void, completed: @escaping () -> Void) -> Disposable {
switch self.content { switch self.content {
case let .complete(_, size): case let .complete(_, size):
next(IndexSet(integersIn: 0 ..< Int(size))) next(RangeSet<Int64>(0 ..< size))
completed() completed()
return EmptyDisposable return EmptyDisposable
case let .partial(file): case let .partial(file):

View File

@ -326,6 +326,17 @@ private enum MultipartFetchSource {
case .revalidate: case .revalidate:
return .fail(.revalidateMediaReference) return .fail(.revalidateMediaReference)
case let .location(parsedLocation): case let .location(parsedLocation):
#if DEBUG
switch parsedLocation {
case let .inputDocumentFileLocation(id, _, _, _):
if id == 5467475273110788326 {
return Signal<Data, MultipartFetchDownloadError>.single(Data(count: Int(limit))) |> delay(0.01, queue: .concurrentDefaultQueue())
}
default:
break
}
#endif
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground) return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground)
|> mapError { error -> MultipartFetchDownloadError in |> mapError { error -> MultipartFetchDownloadError in
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {

View File

@ -269,6 +269,7 @@ swift_library(
"//submodules/BrowserUI:BrowserUI", "//submodules/BrowserUI:BrowserUI",
"//submodules/PremiumUI:PremiumUI", "//submodules/PremiumUI:PremiumUI",
"//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer", "//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer",
"//submodules/Utils/RangeSet:RangeSet",
] + select({ ] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -155,10 +155,19 @@ private final class InlineStickerItemLayer: SimpleLayer {
} }
} }
private var didRequestFrame = false
private func loadNextFrame() { private func loadNextFrame() {
guard let frameSource = self.frameSource else { guard let frameSource = self.frameSource else {
return return
} }
if self.contents != nil {
return
}
if self.didRequestFrame {
return
}
self.didRequestFrame = true
frameSource.with { [weak self] impl in frameSource.with { [weak self] impl in
if let animationFrame = impl.takeFrame(draw: true) { if let animationFrame = impl.takeFrame(draw: true) {
var image: UIImage? var image: UIImage?
@ -191,6 +200,9 @@ private final class InlineStickerItemLayer: SimpleLayer {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if strongSelf.contents != nil {
return
}
strongSelf.contents = image.cgImage strongSelf.contents = image.cgImage
} }
} }
@ -464,9 +476,10 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
/*if item.context.sharedContext.immediateExperimentalUISettings.inlineStickers*/ do { /*if item.context.sharedContext.immediateExperimentalUISettings.inlineStickers*/ do {
var currentCount = 0 var currentCount = 0
let updatedString = NSMutableAttributedString(attributedString: attributedText) let updatedString = NSMutableAttributedString(attributedString: attributedText)
var startIndex = updatedString.string.startIndex
while true { while true {
var hadUpdates = false var hadUpdates = false
updatedString.string.enumerateSubstrings(in: updatedString.string.startIndex ..< updatedString.string.endIndex, options: [.byComposedCharacterSequences]) { substring, substringRange, _, stop in updatedString.string.enumerateSubstrings(in: startIndex ..< updatedString.string.endIndex, options: [.byComposedCharacterSequences]) { substring, substringRange, _, stop in
if let substring = substring { if let substring = substring {
let emoji = substring.basicEmoji.0 let emoji = substring.basicEmoji.0
@ -477,14 +490,22 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} }
if let emojiFile = emojiFile { if let emojiFile = emojiFile {
updatedString.replaceCharacters(in: NSRange(substringRange, in: updatedString.string), with: NSAttributedString(string: "[\u{00a0}\u{00a0}\u{00a0}\u{00a0}\u{00a0}]", attributes: [NSAttributedString.Key("Attribute__EmbeddedItem"): InlineStickerItem(file: emojiFile), NSAttributedString.Key.foregroundColor: UIColor.clear.cgColor])) let currentDict = updatedString.attributes(at: NSRange(substringRange, in: updatedString.string).lowerBound, effectiveRange: nil)
var updatedAttributes: [NSAttributedString.Key: Any] = currentDict
updatedAttributes[NSAttributedString.Key.foregroundColor] = UIColor.clear.cgColor
updatedAttributes[NSAttributedString.Key("Attribute__EmbeddedItem")] = InlineStickerItem(file: emojiFile)
let insertString = NSAttributedString(string: "[\u{00a0}\u{00a0}\u{00a0}]", attributes: updatedAttributes)
//updatedString.insert(insertString, at: NSRange(substringRange, in: updatedString.string).upperBound)
updatedString.replaceCharacters(in: NSRange(substringRange, in: updatedString.string), with: insertString)
startIndex = substringRange.lowerBound
currentCount += 1 currentCount += 1
hadUpdates = true hadUpdates = true
stop = true stop = true
} }
} }
} }
if !hadUpdates || currentCount >= 10 { if !hadUpdates || currentCount >= 1000 {
break break
} }
} }
@ -701,7 +722,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
itemLayer.isVisibleForAnimations = self.isVisibleForAnimations itemLayer.isVisibleForAnimations = self.isVisibleForAnimations
} }
itemLayer.frame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 1.0).center, size: CGSize()).insetBy(dx: -11.0, dy: -11.0) itemLayer.frame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 0.0).center, size: CGSize()).insetBy(dx: -12.0, dy: -12.0)
} }
} }
} }

View File

@ -12,6 +12,7 @@ import AccountContext
import PhotoResources import PhotoResources
import AppBundle import AppBundle
import ManagedAnimationNode import ManagedAnimationNode
import RangeSet
private func generateBackground(theme: PresentationTheme) -> UIImage? { private func generateBackground(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 20.0, height: 10.0 + 8.0), rotatedContext: { size, context in return generateImage(CGSize(width: 20.0, height: 10.0 + 8.0), rotatedContext: { size, context in
@ -401,7 +402,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
strongSelf.currentFileReference = fileReference strongSelf.currentFileReference = fileReference
if let size = fileReference.media.size { if let size = fileReference.media.size {
strongSelf.scrubberNode.bufferingStatus = strongSelf.postbox.mediaBox.resourceRangesStatus(fileReference.media.resource) strongSelf.scrubberNode.bufferingStatus = strongSelf.postbox.mediaBox.resourceRangesStatus(fileReference.media.resource)
|> map { ranges -> (IndexSet, Int64) in |> map { ranges -> (RangeSet<Int64>, Int64) in
return (ranges, size) return (ranges, size)
} }
} else { } else {

View File

@ -22,6 +22,7 @@ swift_library(
"//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyComponents:LegacyComponents",
"//submodules/RadialStatusNode:RadialStatusNode", "//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/Utils/RangeSet:RangeSet",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -10,6 +10,7 @@ import UniversalMediaPlayer
import AccountContext import AccountContext
import PhotoResources import PhotoResources
import UIKitRuntimeUtils import UIKitRuntimeUtils
import RangeSet
public enum NativeVideoContentId: Hashable { public enum NativeVideoContentId: Hashable {
case message(UInt32, MediaId) case message(UInt32, MediaId)
@ -130,8 +131,8 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
} }
} }
private let _bufferingStatus = Promise<(IndexSet, Int64)?>() private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>()
var bufferingStatus: Signal<(IndexSet, Int64)?, NoError> { var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> {
return self._bufferingStatus.get() return self._bufferingStatus.get()
} }

View File

@ -10,6 +10,7 @@ import UniversalMediaPlayer
import TelegramAudio import TelegramAudio
import AccountContext import AccountContext
import PhotoResources import PhotoResources
import RangeSet
public enum PlatformVideoContentId: Hashable { public enum PlatformVideoContentId: Hashable {
case message(MessageId, UInt32, MediaId) case message(MessageId, UInt32, MediaId)
@ -132,8 +133,8 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
return self._status.get() return self._status.get()
} }
private let _bufferingStatus = Promise<(IndexSet, Int64)?>() private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>()
var bufferingStatus: Signal<(IndexSet, Int64)?, NoError> { var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> {
return self._bufferingStatus.get() return self._bufferingStatus.get()
} }

View File

@ -10,6 +10,7 @@ import LegacyComponents
import UniversalMediaPlayer import UniversalMediaPlayer
import AccountContext import AccountContext
import PhotoResources import PhotoResources
import RangeSet
public final class SystemVideoContent: UniversalVideoContent { public final class SystemVideoContent: UniversalVideoContent {
public let id: AnyHashable public let id: AnyHashable
@ -50,8 +51,8 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
return self._status.get() return self._status.get()
} }
private let _bufferingStatus = Promise<(IndexSet, Int64)?>() private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>()
var bufferingStatus: Signal<(IndexSet, Int64)?, NoError> { var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> {
return self._bufferingStatus.get() return self._bufferingStatus.get()
} }

View File

@ -4,6 +4,7 @@ import AsyncDisplayKit
import SwiftSignalKit import SwiftSignalKit
import UniversalMediaPlayer import UniversalMediaPlayer
import AccountContext import AccountContext
import RangeSet
private final class UniversalVideoContentSubscriber { private final class UniversalVideoContentSubscriber {
let id: Int32 let id: Int32
@ -28,11 +29,11 @@ private final class UniversalVideoContentHolder {
var statusValue: MediaPlayerStatus? var statusValue: MediaPlayerStatus?
var bufferingStatusDisposable: Disposable? var bufferingStatusDisposable: Disposable?
var bufferingStatusValue: (IndexSet, Int64)? var bufferingStatusValue: (RangeSet<Int64>, Int64)?
var playbackCompletedIndex: Int? var playbackCompletedIndex: Int?
init(content: UniversalVideoContent, contentNode: UniversalVideoContentNode & ASDisplayNode, statusUpdated: @escaping (MediaPlayerStatus?) -> Void, bufferingStatusUpdated: @escaping ((IndexSet, Int64)?) -> Void, playbackCompleted: @escaping () -> Void) { init(content: UniversalVideoContent, contentNode: UniversalVideoContentNode & ASDisplayNode, statusUpdated: @escaping (MediaPlayerStatus?) -> Void, bufferingStatusUpdated: @escaping ((RangeSet<Int64>, Int64)?) -> Void, playbackCompleted: @escaping () -> Void) {
self.content = content self.content = content
self.contentNode = contentNode self.contentNode = contentNode
@ -131,7 +132,7 @@ private final class UniversalVideoContentHolder {
private final class UniversalVideoContentHolderCallbacks { private final class UniversalVideoContentHolderCallbacks {
let playbackCompleted = Bag<() -> Void>() let playbackCompleted = Bag<() -> Void>()
let status = Bag<(MediaPlayerStatus?) -> Void>() let status = Bag<(MediaPlayerStatus?) -> Void>()
let bufferingStatus = Bag<((IndexSet, Int64)?) -> Void>() let bufferingStatus = Bag<((RangeSet<Int64>, Int64)?) -> Void>()
var isEmpty: Bool { var isEmpty: Bool {
return self.playbackCompleted.isEmpty && self.status.isEmpty && self.bufferingStatus.isEmpty return self.playbackCompleted.isEmpty && self.status.isEmpty && self.bufferingStatus.isEmpty
@ -278,7 +279,7 @@ public final class UniversalVideoManagerImpl: UniversalVideoManager {
} |> runOn(Queue.mainQueue()) } |> runOn(Queue.mainQueue())
} }
public func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(IndexSet, Int64)?, NoError> { public func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(RangeSet<Int64>, Int64)?, NoError> {
return Signal { subscriber in return Signal { subscriber in
var callbacks: UniversalVideoContentHolderCallbacks var callbacks: UniversalVideoContentHolderCallbacks
if let current = self.holderCallbacks[content.id] { if let current = self.holderCallbacks[content.id] {

View File

@ -10,6 +10,7 @@ import UniversalMediaPlayer
import LegacyComponents import LegacyComponents
import AccountContext import AccountContext
import PhotoResources import PhotoResources
import RangeSet
public final class WebEmbedVideoContent: UniversalVideoContent { public final class WebEmbedVideoContent: UniversalVideoContent {
public let id: AnyHashable public let id: AnyHashable
@ -50,8 +51,8 @@ final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoContentNode {
return self._status.get() return self._status.get()
} }
private let _bufferingStatus = Promise<(IndexSet, Int64)?>() private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>()
var bufferingStatus: Signal<(IndexSet, Int64)?, NoError> { var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> {
return self._bufferingStatus.get() return self._bufferingStatus.get()
} }