feat: add expandable file and link controls to playlist
This commit is contained in:
@@ -102,6 +102,7 @@ struct ResolvedPlaybackCheck {
|
||||
model.shutdown()
|
||||
try await streamQueueLifecycle(source: source)
|
||||
try await enqueueLifecycle(source: source)
|
||||
try await sidebarQueueLifecycle(source: source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,6 +364,82 @@ struct ResolvedPlaybackCheck {
|
||||
print("PASS: repeated enqueue while paused/playing, standalone navigation, atomic conflicts, independent reports and empty-player start")
|
||||
}
|
||||
|
||||
static func sidebarQueueLifecycle(source: ResolvedSource) async throws {
|
||||
let suite = "terebi.sidebar-queue-check." + UUID().uuidString
|
||||
let defaults = UserDefaults(suiteName: suite)!
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let first = URL(string: "https://www.youtube.com/watch?v=AAAAAAAAAAA")!
|
||||
let second = URL(string: "https://www.youtube.com/watch?v=BBBBBBBBBBB")!
|
||||
let episode = URL(string: "https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1")!
|
||||
let failedURL = URL(string: "https://example.invalid/failure")!
|
||||
let cancelledURL = URL(string: "https://example.invalid/cancelled")!
|
||||
var requests = 0
|
||||
var pending: CheckedContinuation<YouTubeVideo, Never>?
|
||||
let model = PlayerModel(defaults: defaults, youtubeResolver: { url, _ in
|
||||
requests += 1
|
||||
if url == failedURL { throw CocoaError(.fileReadCorruptFile) }
|
||||
let video = YouTubeVideo(title: "Sidebar \(url == first ? "first" : "second")",
|
||||
video: .init(url: source.url, headers: source.headers), audio: nil,
|
||||
duration: 0, isLive: false, webpageURL: url, height: nil, availableHeights: [])
|
||||
if url == cancelledURL { return await withCheckedContinuation { pending = $0 } }
|
||||
return video
|
||||
}, miruroResolver: { _, _ in source })
|
||||
defer { model.shutdown() }
|
||||
try await model.addStreamToQueue(first.absoluteString)
|
||||
try await until("empty sidebar addition starts fetched video", model: model) {
|
||||
!model.isBusy && model.state.status == PLAYER_PLAYING && model.state.position > 0.2
|
||||
}
|
||||
require(requests == 1 && model.queue.currentURL == first, "empty addition uses fetched source without re-extracting")
|
||||
require(model.setPlayback(playing: false), "pause before sidebar addition")
|
||||
try await until("sidebar pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
|
||||
let selected = model.queue.currentID
|
||||
let revision = model.state.revision
|
||||
let position = model.state.position
|
||||
try await model.addStreamToQueue(" https://youtu.be/BBBBBBBBBBB \n")
|
||||
try await model.addStreamToQueue(episode.absoluteString)
|
||||
require(model.queue.items.map(\.url) == [first, second, episode] &&
|
||||
model.queue.items[1].displayName == "Sidebar second" &&
|
||||
model.queue.items[2].displayName == source.title &&
|
||||
model.queue.currentID == selected && model.state.revision == revision &&
|
||||
model.state.position == position && model.state.status == PLAYER_PAUSED,
|
||||
"fetched YouTube and Miruro additions preserve paused playback, order and titles")
|
||||
for text in ["not a URL", "file:///tmp/video.mp4"] {
|
||||
do {
|
||||
try await model.addStreamToQueue(text)
|
||||
fatalError("FAIL: invalid sidebar URL accepted")
|
||||
} catch {}
|
||||
}
|
||||
do {
|
||||
try await model.addStreamToQueue(failedURL.absoluteString)
|
||||
fatalError("FAIL: failed extraction accepted")
|
||||
} catch {}
|
||||
require(model.queue.items.map(\.url) == [first, second, episode] && model.queue.currentID == selected,
|
||||
"invalid URLs and failed extraction leave queue untouched")
|
||||
require(model.setPlayback(playing: true), "resume before sidebar addition")
|
||||
try await until("sidebar resume acknowledged", model: model) { model.state.status == PLAYER_PLAYING }
|
||||
let playingRevision = model.state.revision
|
||||
try await model.addStreamToQueue(second.absoluteString)
|
||||
require(model.state.revision == playingRevision && model.queue.currentID == selected &&
|
||||
model.state.status == PLAYER_PLAYING, "addition does not restart playing media")
|
||||
let cancelled = Task { try await model.addStreamToQueue(cancelledURL.absoluteString) }
|
||||
while pending == nil { await Task.yield() }
|
||||
cancelled.cancel()
|
||||
pending!.resume(returning: YouTubeVideo(title: "Cancelled", video: .init(url: source.url, headers: source.headers),
|
||||
audio: nil, duration: 0, isLive: false, webpageURL: cancelledURL, height: nil, availableHeights: []))
|
||||
do {
|
||||
try await cancelled.value
|
||||
fatalError("FAIL: cancelled sidebar addition accepted")
|
||||
} catch is CancellationError {}
|
||||
require(model.queue.items.count == 4 && model.queue.currentID == selected,
|
||||
"dismissed in-flight addition cannot append a late result")
|
||||
model.selectQueueItem(model.queue.items[1].id)
|
||||
try await until("sidebar-added item plays through native queue selection", model: model) {
|
||||
!model.isBusy && model.state.status == PLAYER_PLAYING && model.queue.currentURL == second &&
|
||||
model.fileName == "Sidebar second"
|
||||
}
|
||||
print("PASS: sidebar URL fetch/add, empty-player decode, paused/playing preservation, validation, failure, cancellation and selection")
|
||||
}
|
||||
|
||||
static func segmentSkips(_ model: PlayerModel, clock: Clock, file: URL) async throws {
|
||||
let opening = MiruroInterval(kind: .intro, start: 0.5, end: 1.5, episode: 1)
|
||||
let ending = MiruroInterval(kind: .outro, start: 2, end: model.duration, episode: 1)
|
||||
|
||||
Reference in New Issue
Block a user