mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
315 lines
12 KiB
Swift
315 lines
12 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import UniversalMediaPlayer
|
|
import AccountContext
|
|
import RangeSet
|
|
|
|
private final class UniversalVideoContentSubscriber {
|
|
let id: Int32
|
|
let priority: UniversalVideoPriority
|
|
let update: (((UniversalVideoContentNode & ASDisplayNode), Bool)?) -> Void
|
|
var active: Bool = false
|
|
|
|
init(id: Int32, priority: UniversalVideoPriority, update: @escaping (((UniversalVideoContentNode & ASDisplayNode), Bool)?) -> Void) {
|
|
self.id = id
|
|
self.priority = priority
|
|
self.update = update
|
|
}
|
|
}
|
|
|
|
private final class UniversalVideoContentHolder {
|
|
private var nextId: Int32 = 0
|
|
private var subscribers: [UniversalVideoContentSubscriber] = []
|
|
let content: UniversalVideoContent
|
|
let contentNode: UniversalVideoContentNode & ASDisplayNode
|
|
|
|
var statusDisposable: Disposable?
|
|
var statusValue: MediaPlayerStatus?
|
|
|
|
var bufferingStatusDisposable: Disposable?
|
|
var bufferingStatusValue: (RangeSet<Int64>, Int64)?
|
|
|
|
var playbackCompletedIndex: Int?
|
|
|
|
init(content: UniversalVideoContent, contentNode: UniversalVideoContentNode & ASDisplayNode, statusUpdated: @escaping (MediaPlayerStatus?) -> Void, bufferingStatusUpdated: @escaping ((RangeSet<Int64>, Int64)?) -> Void, playbackCompleted: @escaping () -> Void) {
|
|
self.content = content
|
|
self.contentNode = contentNode
|
|
|
|
self.statusDisposable = (contentNode.status |> deliverOnMainQueue).start(next: { [weak self] value in
|
|
if let strongSelf = self {
|
|
strongSelf.statusValue = value
|
|
statusUpdated(value)
|
|
}
|
|
})
|
|
|
|
self.bufferingStatusDisposable = (contentNode.bufferingStatus |> deliverOnMainQueue).start(next: { [weak self] value in
|
|
if let strongSelf = self {
|
|
strongSelf.bufferingStatusValue = value
|
|
bufferingStatusUpdated(value)
|
|
}
|
|
})
|
|
|
|
self.playbackCompletedIndex = contentNode.addPlaybackCompleted {
|
|
playbackCompleted()
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.statusDisposable?.dispose()
|
|
self.bufferingStatusDisposable?.dispose()
|
|
if let playbackCompletedIndex = self.playbackCompletedIndex {
|
|
self.contentNode.removePlaybackCompleted(playbackCompletedIndex)
|
|
}
|
|
}
|
|
|
|
var isEmpty: Bool {
|
|
return self.subscribers.isEmpty
|
|
}
|
|
|
|
func addSubscriber(priority: UniversalVideoPriority, update: @escaping (((UniversalVideoContentNode & ASDisplayNode), Bool)?) -> Void) -> Int32 {
|
|
let id = self.nextId
|
|
self.nextId += 1
|
|
|
|
self.subscribers.append(UniversalVideoContentSubscriber(id: id, priority: priority, update: update))
|
|
self.subscribers.sort(by: { lhs, rhs in
|
|
if lhs.priority != rhs.priority {
|
|
return lhs.priority < rhs.priority
|
|
}
|
|
return lhs.id < rhs.id
|
|
})
|
|
|
|
return id
|
|
}
|
|
|
|
func removeSubscriberAndUpdate(id: Int32) {
|
|
for i in 0 ..< self.subscribers.count {
|
|
if self.subscribers[i].id == id {
|
|
let subscriber = self.subscribers[i]
|
|
self.subscribers.remove(at: i)
|
|
if subscriber.active {
|
|
self.update(removeSubscribers: [subscriber])
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func update(forceUpdateId: Int32? = nil, initiatedCreation: Int32? = nil, removeSubscribers: [UniversalVideoContentSubscriber] = []) {
|
|
var removeSubscribers = removeSubscribers
|
|
for i in (0 ..< self.subscribers.count) {
|
|
if i == self.subscribers.count - 1 {
|
|
if !self.subscribers[i].active {
|
|
self.subscribers[i].active = true
|
|
self.subscribers[i].update((self.contentNode, initiatedCreation: initiatedCreation == self.subscribers[i].id))
|
|
}
|
|
} else {
|
|
if self.subscribers[i].active {
|
|
self.subscribers[i].active = false
|
|
removeSubscribers.append(self.subscribers[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
for subscriber in removeSubscribers {
|
|
subscriber.update(nil)
|
|
}
|
|
|
|
if let forceUpdateId = forceUpdateId {
|
|
for subscriber in self.subscribers {
|
|
if subscriber.id == forceUpdateId {
|
|
if !subscriber.active {
|
|
subscriber.update(nil)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private final class UniversalVideoContentHolderCallbacks {
|
|
let playbackCompleted = Bag<() -> Void>()
|
|
let status = Bag<(MediaPlayerStatus?) -> Void>()
|
|
let bufferingStatus = Bag<((RangeSet<Int64>, Int64)?) -> Void>()
|
|
|
|
var isEmpty: Bool {
|
|
return self.playbackCompleted.isEmpty && self.status.isEmpty && self.bufferingStatus.isEmpty
|
|
}
|
|
}
|
|
|
|
public final class UniversalVideoManagerImpl: UniversalVideoManager {
|
|
private var holders: [AnyHashable: UniversalVideoContentHolder] = [:]
|
|
private var holderCallbacks: [AnyHashable: UniversalVideoContentHolderCallbacks] = [:]
|
|
|
|
public init() {
|
|
}
|
|
|
|
public func attachUniversalVideoContent(content: UniversalVideoContent, priority: UniversalVideoPriority, create: () -> UniversalVideoContentNode & ASDisplayNode, update: @escaping (((UniversalVideoContentNode & ASDisplayNode), Bool)?) -> Void) -> (AnyHashable, Int32) {
|
|
assert(Queue.mainQueue().isCurrent())
|
|
|
|
var initiatedCreation = false
|
|
|
|
let holder: UniversalVideoContentHolder
|
|
if let current = self.holders[content.id] {
|
|
holder = current
|
|
} else {
|
|
let foundHolder: UniversalVideoContentHolder? = nil
|
|
for (_, current) in self.holders {
|
|
if current.content.isEqual(to: content) {
|
|
//foundHolder = current
|
|
break
|
|
}
|
|
}
|
|
if let foundHolder = foundHolder {
|
|
holder = foundHolder
|
|
} else {
|
|
initiatedCreation = true
|
|
holder = UniversalVideoContentHolder(content: content, contentNode: create(), statusUpdated: { [weak self] value in
|
|
if let strongSelf = self {
|
|
if let current = strongSelf.holderCallbacks[content.id] {
|
|
for subscriber in current.status.copyItems() {
|
|
subscriber(value)
|
|
}
|
|
}
|
|
}
|
|
}, bufferingStatusUpdated: { [weak self] value in
|
|
if let strongSelf = self {
|
|
if let current = strongSelf.holderCallbacks[content.id] {
|
|
for subscriber in current.bufferingStatus.copyItems() {
|
|
subscriber(value)
|
|
}
|
|
}
|
|
}
|
|
}, playbackCompleted: { [weak self] in
|
|
if let strongSelf = self {
|
|
if let current = strongSelf.holderCallbacks[content.id] {
|
|
for subscriber in current.playbackCompleted.copyItems() {
|
|
subscriber()
|
|
}
|
|
}
|
|
}
|
|
})
|
|
self.holders[content.id] = holder
|
|
}
|
|
}
|
|
|
|
let id = holder.addSubscriber(priority: priority, update: update)
|
|
holder.update(forceUpdateId: id, initiatedCreation: initiatedCreation ? id : nil)
|
|
return (holder.content.id, id)
|
|
}
|
|
|
|
public func detachUniversalVideoContent(id: AnyHashable, index: Int32) {
|
|
assert(Queue.mainQueue().isCurrent())
|
|
|
|
if let holder = self.holders[id] {
|
|
holder.removeSubscriberAndUpdate(id: index)
|
|
if holder.isEmpty {
|
|
self.holders.removeValue(forKey: id)
|
|
|
|
if let current = self.holderCallbacks[id] {
|
|
for subscriber in current.status.copyItems() {
|
|
subscriber(nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func withUniversalVideoContent(id: AnyHashable, _ f: ((UniversalVideoContentNode & ASDisplayNode)?) -> Void) {
|
|
if let holder = self.holders[id] {
|
|
f(holder.contentNode)
|
|
} else {
|
|
f(nil)
|
|
}
|
|
}
|
|
|
|
public func addPlaybackCompleted(id: AnyHashable, _ f: @escaping () -> Void) -> Int {
|
|
assert(Queue.mainQueue().isCurrent())
|
|
var callbacks: UniversalVideoContentHolderCallbacks
|
|
if let current = self.holderCallbacks[id] {
|
|
callbacks = current
|
|
} else {
|
|
callbacks = UniversalVideoContentHolderCallbacks()
|
|
self.holderCallbacks[id] = callbacks
|
|
}
|
|
return callbacks.playbackCompleted.add(f)
|
|
}
|
|
|
|
public func removePlaybackCompleted(id: AnyHashable, index: Int) {
|
|
if let current = self.holderCallbacks[id] {
|
|
current.playbackCompleted.remove(index)
|
|
if current.playbackCompleted.isEmpty {
|
|
self.holderCallbacks.removeValue(forKey: id)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func statusSignal(content: UniversalVideoContent) -> Signal<MediaPlayerStatus?, NoError> {
|
|
return Signal { subscriber in
|
|
var callbacks: UniversalVideoContentHolderCallbacks
|
|
if let current = self.holderCallbacks[content.id] {
|
|
callbacks = current
|
|
} else {
|
|
callbacks = UniversalVideoContentHolderCallbacks()
|
|
self.holderCallbacks[content.id] = callbacks
|
|
}
|
|
|
|
let index = callbacks.status.add({ value in
|
|
subscriber.putNext(value)
|
|
})
|
|
|
|
if let current = self.holders[content.id] {
|
|
subscriber.putNext(current.statusValue)
|
|
} else {
|
|
subscriber.putNext(nil)
|
|
}
|
|
|
|
return ActionDisposable {
|
|
Queue.mainQueue().async {
|
|
if let current = self.holderCallbacks[content.id] {
|
|
current.status.remove(index)
|
|
if current.playbackCompleted.isEmpty {
|
|
self.holderCallbacks.removeValue(forKey: content.id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |> runOn(Queue.mainQueue())
|
|
}
|
|
|
|
public func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(RangeSet<Int64>, Int64)?, NoError> {
|
|
return Signal { subscriber in
|
|
var callbacks: UniversalVideoContentHolderCallbacks
|
|
if let current = self.holderCallbacks[content.id] {
|
|
callbacks = current
|
|
} else {
|
|
callbacks = UniversalVideoContentHolderCallbacks()
|
|
self.holderCallbacks[content.id] = callbacks
|
|
}
|
|
|
|
let index = callbacks.bufferingStatus.add({ value in
|
|
subscriber.putNext(value)
|
|
})
|
|
|
|
if let current = self.holders[content.id] {
|
|
subscriber.putNext(current.bufferingStatusValue)
|
|
} else {
|
|
subscriber.putNext(nil)
|
|
}
|
|
|
|
return ActionDisposable {
|
|
Queue.mainQueue().async {
|
|
if let current = self.holderCallbacks[content.id] {
|
|
current.status.remove(index)
|
|
if current.playbackCompleted.isEmpty {
|
|
self.holderCallbacks.removeValue(forKey: content.id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |> runOn(Queue.mainQueue())
|
|
}
|
|
}
|