Add loading external subtitle files for media-source playback

Adds a "Load subtitle from file…" row to the Subtitles section on iOS
(document picker) and macOS (open panel), available when playing files
from local folder, SMB, or WebDAV sources. The picked file is copied
into the per-video temp subtitle directory, registered as a selectable
Caption (displayed by filename), and activated through the existing
loadCaption flow. The Subtitles tab and captions control button now
also appear for media-source files without any subtitles.
This commit is contained in:
Arkadiusz Fal
2026-07-30 23:59:26 +02:00
parent 6a0fe2fbbf
commit 8faf20c9e9
8 changed files with 238 additions and 4 deletions

View File

@@ -0,0 +1,74 @@
//
// SubtitleFilePicker.swift
// Yattee
//
// File pickers for loading an external subtitle file into the current playback.
// iOS uses a document picker sheet, macOS an NSOpenPanel. Not available on tvOS.
//
import SwiftUI
import UniformTypeIdentifiers
#if !os(tvOS)
/// Content types accepted by the subtitle file pickers.
enum SubtitleFileTypes {
/// Dynamic UTTypes for the supported subtitle extensions (srt/vtt/ass/ssa/sub
/// none of them have built-in UTType constants).
static var contentTypes: [UTType] {
let types = MediaFile.subtitleExtensions.compactMap { UTType(filenameExtension: $0) }
return types.isEmpty ? [.data] : types
}
}
#if os(iOS)
struct SubtitleFilePickerView: UIViewControllerRepresentable {
let onSelect: (URL) -> Void
func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
let picker = UIDocumentPickerViewController(forOpeningContentTypes: SubtitleFileTypes.contentTypes)
picker.delegate = context.coordinator
picker.allowsMultipleSelection = false
return picker
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(onSelect: onSelect)
}
final class Coordinator: NSObject, UIDocumentPickerDelegate {
let onSelect: (URL) -> Void
init(onSelect: @escaping (URL) -> Void) {
self.onSelect = onSelect
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
onSelect(url)
}
}
}
#endif
#if os(macOS)
enum SubtitleFilePanel {
@MainActor
static func present(onSelect: (URL) -> Void) {
let panel = NSOpenPanel()
panel.allowedContentTypes = SubtitleFileTypes.contentTypes
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowsMultipleSelection = false
panel.message = String(localized: "stream.subtitles.loadFromFile.message")
if panel.runModal() == .OK, let url = panel.url {
onSelect(url)
}
}
}
#endif
#endif