Files
simvision/KSPlayer-main/Sources/KSPlayer/MEPlayer/CircularBuffer.swift
Michael Simard 872354b834 Initial commit: SimVision tvOS streaming app
Features:
- VOD library with movie grouping and version detection
- TV show library with season/episode organization
- TMDB integration for trending shows and recently aired episodes
- Recent releases section with TMDB release date sorting
- Watch history tracking with continue watching
- Playlist caching (12-hour TTL) for offline support
- M3U playlist parsing with XStream API support
- Authentication with credential storage

Technical:
- SwiftUI for tvOS
- Actor-based services for thread safety
- Persistent caching for playlists, TMDB data, and watch history
- KSPlayer integration for video playback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 22:12:08 -06:00

184 lines
6.2 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// CircularBuffer.swift
// KSPlayer
//
// Created by kintan on 2018/3/9.
//
import Foundation
///
/// seek
public class CircularBuffer<Item: ObjectQueueItem> {
private var _buffer = ContiguousArray<Item?>()
// private let semaphore = DispatchSemaphore(value: 0)
private let condition = NSCondition()
private var headIndex = UInt(0)
private var tailIndex = UInt(0)
private let expanding: Bool
private let sorted: Bool
private var destroyed = false
@inline(__always)
private var _count: Int { Int(tailIndex &- headIndex) }
@inline(__always)
public var count: Int {
// condition.lock()
// defer { condition.unlock() }
Int(tailIndex &- headIndex)
}
public internal(set) var fps: Float = 24
public private(set) var maxCount: Int
private var mask: UInt
public init(initialCapacity: Int = 256, sorted: Bool = false, expanding: Bool = true) {
self.expanding = expanding
self.sorted = sorted
let capacity = initialCapacity.nextPowerOf2()
_buffer = ContiguousArray<Item?>(repeating: nil, count: Int(capacity))
maxCount = Int(capacity)
mask = UInt(maxCount - 1)
assert(_buffer.count == capacity)
}
public func push(_ value: Item) {
condition.lock()
defer { condition.unlock() }
if destroyed {
return
}
if _buffer[Int(tailIndex & mask)] != nil {
assertionFailure("value is not nil of headIndex: \(headIndex),tailIndex: \(tailIndex), bufferCount: \(_buffer.count), mask: \(mask)")
}
_buffer[Int(tailIndex & mask)] = value
if sorted {
// sort
var index = tailIndex
while index > headIndex {
guard let item = _buffer[Int((index - 1) & mask)] else {
assertionFailure("value is nil of index: \((index - 1) & mask) headIndex: \(headIndex),tailIndex: \(tailIndex), bufferCount: \(_buffer.count), mask: \(mask)")
break
}
if item.timestamp <= _buffer[Int(index & mask)]!.timestamp {
break
}
_buffer.swapAt(Int((index - 1) & mask), Int(index & mask))
index -= 1
}
}
tailIndex &+= 1
if _count >= maxCount {
if expanding {
// No more room left for another append so grow the buffer now.
_doubleCapacity()
} else {
condition.wait()
}
} else {
// signal
if _count == 1 {
condition.signal()
}
}
}
public func pop(wait: Bool = false, where predicate: ((Item, Int) -> Bool)? = nil) -> Item? {
condition.lock()
defer { condition.unlock() }
if destroyed {
return nil
}
if headIndex == tailIndex {
if wait {
condition.wait()
if destroyed || headIndex == tailIndex {
return nil
}
} else {
return nil
}
}
let index = Int(headIndex & mask)
guard let item = _buffer[index] else {
assertionFailure("value is nil of index: \(index) headIndex: \(headIndex),tailIndex: \(tailIndex), bufferCount: \(_buffer.count), mask: \(mask)")
return nil
}
if let predicate, !predicate(item, _count) {
return nil
} else {
headIndex &+= 1
_buffer[index] = nil
if _count == maxCount >> 1 {
condition.signal()
}
return item
}
}
public func search(where predicate: (Item) -> Bool) -> [Item] {
condition.lock()
defer { condition.unlock() }
var i = headIndex
var result = [Item]()
while i < tailIndex {
if let item = _buffer[Int(i & mask)] {
if predicate(item) {
result.append(item)
_buffer[Int(i & mask)] = nil
headIndex = i + 1
}
} else {
assertionFailure("value is nil of index: \(i) headIndex: \(headIndex), tailIndex: \(tailIndex), bufferCount: \(_buffer.count), mask: \(mask)")
return result
}
i += 1
}
return result
}
public func flush() {
condition.lock()
defer { condition.unlock() }
headIndex = 0
tailIndex = 0
_buffer.removeAll(keepingCapacity: !destroyed)
_buffer.append(contentsOf: ContiguousArray<Item?>(repeating: nil, count: destroyed ? 1 : maxCount))
condition.broadcast()
}
public func shutdown() {
destroyed = true
flush()
}
private func _doubleCapacity() {
var newBacking: ContiguousArray<Item?> = []
let newCapacity = maxCount << 1 // Double the storage.
precondition(newCapacity > 0, "Can't double capacity of \(_buffer.count)")
assert(newCapacity % 2 == 0)
newBacking.reserveCapacity(newCapacity)
let head = Int(headIndex & mask)
newBacking.append(contentsOf: _buffer[head ..< maxCount])
if head > 0 {
newBacking.append(contentsOf: _buffer[0 ..< head])
}
let repeatitionCount = newCapacity &- newBacking.count
newBacking.append(contentsOf: repeatElement(nil, count: repeatitionCount))
headIndex = 0
tailIndex = UInt(newBacking.count &- repeatitionCount)
_buffer = newBacking
maxCount = newCapacity
mask = UInt(maxCount - 1)
}
}
extension FixedWidthInteger {
/// Returns the next power of two.
@inline(__always)
func nextPowerOf2() -> Self {
guard self != 0 else {
return 1
}
return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
}
}