From 300409da061273e84e60f6e33bdcf8d351c8d338 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Tue, 8 Sep 2026 04:10:37 +0200 Subject: [PATCH] Auto-apply local sidecar subtitles on iOS playback start. Co-authored-by: Cursor --- apps/ios/VMC/Data/Models.swift | 11 +++++++++ apps/ios/VMC/Playback/PlayerScreen.swift | 29 ++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/apps/ios/VMC/Data/Models.swift b/apps/ios/VMC/Data/Models.swift index bebb677..0d7eecf 100644 --- a/apps/ios/VMC/Data/Models.swift +++ b/apps/ios/VMC/Data/Models.swift @@ -52,6 +52,17 @@ struct PlayResult: Decodable { let streamUrl: String let sessionId: String? let mediaFileId: String? + let defaultSubtitle: DefaultSubtitle? +} + +struct DefaultSubtitle: Decodable { + let fileId: Int? + let source: String? + let language: String? + let fileName: String? + let format: String? + let contentBase64: String + let label: String? } struct ViewerProfile: Decodable, Identifiable, Hashable { diff --git a/apps/ios/VMC/Playback/PlayerScreen.swift b/apps/ios/VMC/Playback/PlayerScreen.swift index 2025341..f98c815 100644 --- a/apps/ios/VMC/Playback/PlayerScreen.swift +++ b/apps/ios/VMC/Playback/PlayerScreen.swift @@ -12,6 +12,7 @@ struct PlayRequest: Identifiable { let startMs: Int64 let isLive: Bool let fallbackUrl: URL? + let subtitleFileURL: URL? static func vod( url: URL, @@ -21,12 +22,13 @@ struct PlayRequest: Identifiable { movieId: String?, episodeId: String?, seriesId: String?, - startMs: Int64 + startMs: Int64, + subtitleFileURL: URL? = nil ) -> PlayRequest { PlayRequest( url: url, title: title, mediaType: mediaType, mediaFileId: mediaFileId, movieId: movieId, episodeId: episodeId, seriesId: seriesId, - startMs: startMs, isLive: false, fallbackUrl: nil + startMs: startMs, isLive: false, fallbackUrl: nil, subtitleFileURL: subtitleFileURL ) } @@ -34,7 +36,7 @@ struct PlayRequest: Identifiable { PlayRequest( url: url, title: title, mediaType: "live", mediaFileId: nil, movieId: nil, episodeId: nil, seriesId: nil, - startMs: 0, isLive: true, fallbackUrl: fallback + startMs: 0, isLive: true, fallbackUrl: fallback, subtitleFileURL: nil ) } } @@ -361,7 +363,7 @@ struct PlayerScreen: View { if request.isLive { player.playLive(url: request.url, fallback: request.fallbackUrl) } else { - player.play(url: request.url, startMs: request.startMs) + player.play(url: request.url, startMs: request.startMs, subtitleFileURL: request.subtitleFileURL) progressTask = Task { await progressLoop() } } try? await Task.sleep(nanoseconds: 900_000_000) @@ -468,9 +470,26 @@ struct PlayLauncher: View { guard let url = URL(string: play.streamUrl) else { throw ApiError.message("Ongeldige stream-URL") } + var subtitleURL: URL? + if let sub = play.defaultSubtitle, + let data = Data(base64Encoded: sub.contentBase64), + !data.isEmpty { + let ext: String = { + let f = (sub.format ?? "").lowercased() + if f == "vtt" { return "vtt" } + if f == "ass" || f == "ssa" { return "ass" } + return "srt" + }() + let fileId = sub.fileId ?? 0 + let path = FileManager.default.temporaryDirectory + .appendingPathComponent("vmc-auto-sub-\(fileId).\(ext)") + try data.write(to: path, options: .atomic) + subtitleURL = path + } request = .vod( url: url, title: title, mediaType: mediaType, mediaFileId: mediaFileId, - movieId: movieId, episodeId: episodeId, seriesId: seriesId, startMs: startMs + movieId: movieId, episodeId: episodeId, seriesId: seriesId, startMs: startMs, + subtitleFileURL: subtitleURL ) loading = false } catch {