Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Fixed

- SideloadedSubtitle
- Fixed an issue where internal network requests would cause a user agent mismatch.

## [11.0.1] - 2026-04-21

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class AVSubtitlesLoader: NSObject {
private let synchronizer: SubtitlesSynchronizer?
private let _id: String
private var variantTotalDuration: Double = 0
private var requestMap: [URL: URLRequest] = [:]

init(subtitles: [TextTrackDescription], id: String, player: THEOplayer? = nil) {
self._subtitles = subtitles
Expand All @@ -42,8 +43,8 @@ class AVSubtitlesLoader: NSObject {
}
#endif

func handleMasterManifestRequest(_ url: URL) async -> Data? {
let parser = MasterPlaylistParser(url: url)
func handleMasterManifestRequest(_ url: URL, request: URLRequest?) async -> Data? {
let parser = MasterPlaylistParser(url: url, request: request)
guard let responseData = await parser.sideLoadSubtitles(subtitles: subtitles) else {
print("[AVSubtitlesLoader] ERROR: Couldn't find manifest data")
return nil
Expand All @@ -52,8 +53,8 @@ class AVSubtitlesLoader: NSObject {
return responseData
}

func handleVariantManifest(_ url: URL) async -> Data? {
let parser = VariantPlaylistParser(url: url)
func handleVariantManifest(_ url: URL, request: URLRequest?) async -> Data? {
let parser = VariantPlaylistParser(url: url, request: request)

guard let playlist = await parser.parse(),
let responseData = playlist.manifestData else {
Expand Down Expand Up @@ -163,8 +164,13 @@ enum URLScheme: String {
}

extension AVSubtitlesLoader: MediaPlaylistInterceptor {
func shouldInterceptPlaylistRequest(type: HlsPlaylistType) -> Bool { false }
func didInterceptPlaylistRequest(type: HlsPlaylistType, request: URLRequest) async throws -> URLRequest { request }
func shouldInterceptPlaylistRequest(type: HlsPlaylistType) -> Bool { true }
func didInterceptPlaylistRequest(type: HlsPlaylistType, request: URLRequest) async throws -> URLRequest {
if let url = request.url {
requestMap[url] = request
}
return request
}

func failedToPerformURLRequest(request: URLRequest, response: URLResponse) {
if THEOplayerConnectorSideloadedSubtitle.SHOW_DEBUG_LOGS {
Expand All @@ -190,11 +196,11 @@ extension AVSubtitlesLoader: MediaPlaylistInterceptor {
// intercept the master manifest to append the subtitles
subtitles = await validateSubtitles()
if subtitles.isEmpty { return data }
return await self.handleMasterManifestRequest(url) ?? data
return await self.handleMasterManifestRequest(url, request: requestMap[url]) ?? data
case .video:
// intercept the variant manifest to get the duration
if subtitles.isEmpty { return data }
return await self.handleVariantManifest(url) ?? data
return await self.handleVariantManifest(url, request: requestMap[url]) ?? data
case .subtitles:
// intercept the subtitle request to respond with the HLS subtitle
if subtitles.isEmpty { return data }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ class MasterPlaylistParser: PlaylistParser {
var constructedManifestArray = [String]()
fileprivate var lastMediaLine: Int?
fileprivate let subtitlesGroupId = "THEOsubs"

override init(url: URL) {
super.init(url: url)
}

func sideLoadSubtitles(subtitles: [TextTrackDescription]) async -> Data? {
guard let _ = await self.loadManifest() else { return nil }
let headers = request?.allHTTPHeaderFields
guard let _ = await self.loadManifest(headers) else { return nil }
self.parseManifest()
self.appendSubtitlesLines(subtitles: subtitles)
let constructed = self.constructedManifestArray.joined(separator: "\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ import Foundation
class PlaylistParser {
var manifestURL: URL
var manifestData: Data?
let request: URLRequest?

init(url: URL) {
init(url: URL, request: URLRequest?) {
self.manifestURL = url
self.manifestData = nil
self.request = request
}

func loadManifest() async -> Data? {
if let (data, response) = try? await URLSession.shared.data(from: self.manifestURL) {
func loadManifest(_ headers: [String: String]? = nil) async -> Data? {
var request = URLRequest(url: self.manifestURL)
if let headers {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got a couple questions:

  1. Why do we actually need to intercept the response only to re-request the same URL? Why couldn't we decode the response Data, and embed the subtitle information in it? I might be missing something here
  2. More importantly, could we instead of copying the headers, just replace the url on the request?:
var modifiedRequest: URLRequest = request
modifiedRequest.url = manifestURL

That way we don't need to copy over the headers, and retain the request almost exactly as it should be. Also, since manifestURL is effectively the same url coming from the response URL, we could argue that we don't need to replace the URL?

for header in headers {
request.setValue(header.value, forHTTPHeaderField: header.key)
}
}
if let (data, response) = try? await URLSession.shared.data(for: request) {
// Update the manifestUrl to the url received in the response (to pickup possible url redirect)
if let responseUrl = response.url {
self.manifestURL = responseUrl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ class VariantPlaylistParser: PlaylistParser {
fileprivate(set) var totalPlayListDuration: Double
var constructedManifestArray = [String]()

override init(url: URL) {
override init(url: URL, request: URLRequest?) {
self.totalPlayListDuration = 0
super.init(url: url)
super.init(url: url, request: request)
}

func parse() async -> VariantPlaylistParser? {
guard let _ = await self.loadManifest() else { return nil }
let headers = request?.allHTTPHeaderFields
guard let _ = await self.loadManifest(headers) else { return nil }
self.parseManifest()
let constructed = self.constructedManifestArray.joined(separator: "\n")
if THEOplayerConnectorSideloadedSubtitle.SHOW_DEBUG_LOGS {
Expand Down
Loading