Dreamio turn document
Start Buffering Before Captions
Native playback now makes the media open request the explicit first startup action, while caption resolution is queued as background work and resolved in parallel.
Summary
Confirmed the startup path and tightened it so VLC begins opening the media before subtitle loading work starts. Caption candidates are still accepted at startup and later discovery time, but resolution no longer proceeds one by one.
Changes Made
- Extracted startup caption work into
startCaptionLoadingInBackground(), called only afterbackend.play(request:). - Changed subtitle candidate resolution to run with a task group, preserving the original candidate order after parallel resolution.
- Added a debug log that records startup captions were queued after playback was requested.
Context
The existing startup path already presented native playback without waiting for late captions: DreamioWebViewController resolves and presents the player, then forwards buffered and newly discovered subtitle candidates. Inside NativePlayerViewController, playback was requested before captions were attached. This change makes that ordering explicit and reduces caption resolution latency while media buffering is underway.
Important Implementation Details
startPlayback()still starts the startup timeout, requests VLC playback, then reveals controls.- Caption loading remains non-blocking because
addSubtitleCandidatesqueues work in a SwiftTask. - Parallel subtitle resolution sorts by original index before attaching, so menu ordering remains stable.
Relevant Diff Snippets
Dreamio/NativePlayerViewController.swift · playback-first caption loading
257 unmodified lines25825926026126226326426526626726826927038 unmodified lines309310311312313314315316317318257 unmodified lines}private func resolveSubtitleCandidates(_ candidates: [SubtitleCandidate]) async -> [SubtitleCandidate] {var resolved: [SubtitleCandidate] = []for candidate in candidates {if let playableCandidate = await subtitleResolver.resolve(candidate) {resolved.append(playableCandidate)}}return resolved}private func configureBackend() {38 unmodified linesfailureContainer.isHidden = truestartStartupTimer()backend.play(request: request)addSubtitleCandidates(request.subtitleCandidates)revealControls()}private func startStartupTimer() {startupTimer?.invalidate()startupTimer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) { [weak self] _ in257 unmodified lines25825926026126226326426526626726826927027127227327427527627727827928028138 unmodified lines320321322323324325326327328329330331332333334335336257 unmodified lines}private func resolveSubtitleCandidates(_ candidates: [SubtitleCandidate]) async -> [SubtitleCandidate] {await withTaskGroup(of: (Int, SubtitleCandidate?).self) { group infor (index, candidate) in candidates.enumerated() {group.addTask { [subtitleResolver] in(index, await subtitleResolver.resolve(candidate))}}var resolvedCandidates: [(index: Int, candidate: SubtitleCandidate)] = []for await (index, candidate) in group {if let candidate {resolvedCandidates.append((index, candidate))}}return resolvedCandidates.sorted { $0.index < $1.index }.map(\.candidate)}}private func configureBackend() {38 unmodified linesfailureContainer.isHidden = truestartStartupTimer()backend.play(request: request)startCaptionLoadingInBackground()revealControls()}private func startCaptionLoadingInBackground() {let queuedCount = addSubtitleCandidates(request.subtitleCandidates)#if DEBUGprint("[DreamioNativePlayer] startup captions queued=\(queuedCount) total=\(request.subtitleCandidates.count) playbackAlreadyRequested=true")#endif}private func startStartupTimer() {startupTimer?.invalidate()startupTimer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) { [weak self] _ in
Diff rendered with @pierre/diffs/ssr using a single-file patch.
Expected Impact for End-Users
Streams should begin opening and buffering as soon as the native player starts. External captions can continue resolving and appearing shortly after playback starts, with less delay when multiple caption candidates need network resolution.
Validation
- Passed:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -configuration Debug -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO build. - Manual device playback validation remains recommended for real VLC buffering timing and external subtitle provider latency.
Issues, Limitations, and Mitigations
- No user-facing loading copy was changed. The existing “Opening stream…” text remains scoped to media startup rather than captions.
- Subtitle provider failures are still handled as missing captions, not media playback failures.
Follow-up Work
- Add a dedicated UIKit test seam for startup ordering if the project introduces an iOS unit test target for
NativePlayerViewController. - Manually verify on device with a stream that has several OpenSubtitles candidates.