Redesign Hope iOS UI, harden playback, and document project handoff

This commit is contained in:
snowspeeder committed 2026-09-09 20:23:44 -04:00
1 parent 40aaf4fea7
commit 24f95639dc
24 files changed
+1470 -594

No files matched your search

+28 -10
View File
@@ -1,5 +1,6 @@
import AVFoundation
import Foundation
import UniformTypeIdentifiers
import os
private let logger = Logger(subsystem: "dev.shane.voice", category: "media")
@@ -114,21 +115,29 @@ final class PinnedMediaResourceLoader: NSObject, AVAssetResourceLoaderDelegate {
shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest
) -> Bool {
// All state access is on the main queue (the delegate queue).
guard let dataRequest = loadingRequest.dataRequest else { return false }
guard loadingRequest.dataRequest != nil else { return false }
guard let url = loaderURLs[ObjectIdentifier(resourceLoader)] else {
logger.log("no resolved URL for resource loader")
loadingRequest.finishLoading(with: URLError(.badURL))
return false
}
fetchChunk(loadingRequest, url: url)
return true
}
/// Return bounded chunks promptly instead of buffering an entire song before
/// AVPlayer receives its first usable bytes. The same loading request stays
/// open until its requested range (or EOF) has been delivered.
private func fetchChunk(_ loadingRequest: AVAssetResourceLoadingRequest, url: URL) {
guard !loadingRequest.isCancelled, let dataRequest = loadingRequest.dataRequest else { return }
var upstream = URLRequest(url: url)
let offset = dataRequest.requestedOffset
let offset = max(dataRequest.requestedOffset, dataRequest.currentOffset)
let length = Int64(dataRequest.requestedLength)
if length > 0 {
upstream.setValue("bytes=\(offset)-\(offset + length - 1)", forHTTPHeaderField: "Range")
} else {
upstream.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let requestedEnd = dataRequest.requestedOffset + length
let end = dataRequest.requestsAllDataToEndOfResource || length == 0
? offset + 262_143 : min(requestedEnd - 1, offset + 262_143)
upstream.setValue("bytes=\(offset)-\(end)", forHTTPHeaderField: "Range")
logger.log("fetching \(url.absoluteString, privacy: .public) range=\(upstream.value(forHTTPHeaderField: "Range") ?? "-", privacy: .public)")
// Key the task by the loading request's identity directly — no hash
@@ -140,6 +149,7 @@ final class PinnedMediaResourceLoader: NSObject, AVAssetResourceLoaderDelegate {
// Runs on .main (the session's delegateQueue), same domain as all
// other map mutations.
self.activeTasks[requestKey] = nil
guard !loadingRequest.isCancelled else { return }
if let error {
logger.log("fetch failed \(url.absoluteString, privacy: .public): \(error.localizedDescription, privacy: .public)")
@@ -158,7 +168,8 @@ final class PinnedMediaResourceLoader: NSObject, AVAssetResourceLoaderDelegate {
return
}
if let info = loadingRequest.contentInformationRequest {
info.contentType = http.mimeType ?? "application/octet-stream"
// AVFoundation requires a UTI, not the HTTP MIME string.
info.contentType = http.mimeType.flatMap { UTType(mimeType: $0)?.identifier }
// Content-length handling (Iteration 05 Item 7):
// - For 206: set full content length ONLY when Content-Range
// provides a known total. Never fall back to the range's
@@ -180,14 +191,21 @@ final class PinnedMediaResourceLoader: NSObject, AVAssetResourceLoaderDelegate {
}
info.isByteRangeAccessSupported = true
}
if let data {
if let data, !data.isEmpty {
dataRequest.respond(with: data)
let nextOffset = offset + Int64(data.count)
let total = Self.parseTotalLength(http)
let reachedEOF = total.map { nextOffset >= $0 } ?? (nextOffset <= end)
let needsMore = dataRequest.requestsAllDataToEndOfResource || nextOffset < requestedEnd
if http.statusCode == 206, !reachedEOF, needsMore {
self.fetchChunk(loadingRequest, url: url)
return
}
}
loadingRequest.finishLoading()
}
activeTasks[requestKey] = task
task.resume()
return true
}
func resourceLoader(