From 828349b2725fee7f8448aaca32e821ce1be4fde5 Mon Sep 17 00:00:00 2001 From: Raffi Date: Thu, 27 Nov 2025 13:53:42 +0100 Subject: [PATCH 1/3] filter out invalid subtitle sources --- ...{URL+Scheme.swift => URL+Extensions.swift} | 21 +++++++++++++++++ .../Parsers/MasterPlaylistParser.swift | 23 +++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) rename Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/{URL+Scheme.swift => URL+Extensions.swift} (59%) diff --git a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Scheme.swift b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Extensions.swift similarity index 59% rename from Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Scheme.swift rename to Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Extensions.swift index 15d403af..902ec0ac 100644 --- a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Scheme.swift +++ b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Extensions.swift @@ -28,4 +28,25 @@ extension URL { } return URL(string: self.absoluteString.replacingCharacters(in: range, with: newScheme + "://")) } + + var isValid: Bool { + guard self.scheme != nil else { + print("URL scheme is invalid or missing.") + return false + } + + guard let host = self.host, + !host.isEmpty else { + print("URL host is invalid or missing.") + return false + } + + let disallowedCharacterSet = CharacterSet.urlQueryAllowed.inverted + guard self.absoluteString.rangeOfCharacter(from: disallowedCharacterSet) == nil else { + print("URL contains invalid characters.") + return false + } + + return true + } } diff --git a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift index d83af061..41ecad3e 100644 --- a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift +++ b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift @@ -12,6 +12,8 @@ class MasterPlaylistParser: PlaylistParser { var constructedManifestArray = [String]() fileprivate var lastMediaLine: Int? fileprivate let subtitlesGroupId = "THEOsubs" + private var extXMediaSubtitlesOriginallyExists = false + override init(url: URL) { super.init(url: url) } @@ -19,8 +21,25 @@ class MasterPlaylistParser: PlaylistParser { func sideLoadSubtitles(subtitles: [TextTrackDescription], completion: @escaping (_ data: Data?) -> ()) { self.loadManifest { succ in if succ { + let validSubtitles = subtitles.filter { subtitle in + guard subtitle.src.isValid == true else { + print("The provided subtitle source \(subtitle.src.absoluteString) is invalid") + return false + } + return true + } self.parseManifest() - self.appendSubtitlesLines(subtitles: subtitles) + self.appendSubtitlesLines(subtitles: validSubtitles) + if !self.extXMediaSubtitlesOriginallyExists, + validSubtitles.isEmpty { + // when the original m3u8 does not have any subtitles, and there are no valid subtitles to sideload + // then we remove the added subs attribute from the ExtXStreamInf entries + for (i, ele) in self.constructedManifestArray.enumerated() { + let line = HLSLine(lineString: ele) + line.paramsObject.removeValue(forKey: HLSKeywords.subtitles.rawValue) + self.constructedManifestArray[i] = line.joinLine() + } + } let constructed = self.constructedManifestArray.joined(separator: "\n") if THEOplayerConnectorSideloadedSubtitle.SHOW_DEBUG_LOGS { @@ -58,7 +77,7 @@ class MasterPlaylistParser: PlaylistParser { case HLSKeywords.subtitles.rawValue: line.paramsObject[HLSKeywords.groupId.rawValue] = "\"\(self.subtitlesGroupId)\"" self.lastMediaLine = self.constructedManifestArray.count - + self.extXMediaSubtitlesOriginallyExists = true default: break } From cebb512171e8507d535c902f8af29722d762aceb Mon Sep 17 00:00:00 2001 From: Raffi Date: Thu, 27 Nov 2025 13:55:16 +0100 Subject: [PATCH 2/3] add changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2346cee9..9e7ae664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +<<<<<<< HEAD ### Changed - Conviva - Bumped the Conviva SDK dependency to 4.2.4. +======= +### Fixed + +- SideloadedSubtitle + - Fixed an issue where the stream would not play if an invalid subtitle source is passed and activated. +>>>>>>> 22a7075 (add changelog) ## [10.4.0] - 2025-11-05 From 27b80d579a7fa91a02a44ec44ce883a2ac53aa5d Mon Sep 17 00:00:00 2001 From: Raffi Date: Thu, 27 Nov 2025 14:17:20 +0100 Subject: [PATCH 3/3] use existing source validator --- .../Extensions/URL+Extensions.swift | 6 ++--- .../Parsers/MasterPlaylistParser.swift | 22 ++----------------- .../Utils/SourceValidator.swift | 9 +++++++- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Extensions.swift b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Extensions.swift index 902ec0ac..ae767e88 100644 --- a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Extensions.swift +++ b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Extensions/URL+Extensions.swift @@ -31,19 +31,19 @@ extension URL { var isValid: Bool { guard self.scheme != nil else { - print("URL scheme is invalid or missing.") + print("[AVSubtitlesLoader] URL scheme is invalid or missing.") return false } guard let host = self.host, !host.isEmpty else { - print("URL host is invalid or missing.") + print("[AVSubtitlesLoader] URL host is invalid or missing.") return false } let disallowedCharacterSet = CharacterSet.urlQueryAllowed.inverted guard self.absoluteString.rangeOfCharacter(from: disallowedCharacterSet) == nil else { - print("URL contains invalid characters.") + print("[AVSubtitlesLoader] URL contains invalid characters.") return false } diff --git a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift index 41ecad3e..218aa558 100644 --- a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift +++ b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Parsers/MasterPlaylistParser.swift @@ -12,7 +12,6 @@ class MasterPlaylistParser: PlaylistParser { var constructedManifestArray = [String]() fileprivate var lastMediaLine: Int? fileprivate let subtitlesGroupId = "THEOsubs" - private var extXMediaSubtitlesOriginallyExists = false override init(url: URL) { super.init(url: url) @@ -21,25 +20,8 @@ class MasterPlaylistParser: PlaylistParser { func sideLoadSubtitles(subtitles: [TextTrackDescription], completion: @escaping (_ data: Data?) -> ()) { self.loadManifest { succ in if succ { - let validSubtitles = subtitles.filter { subtitle in - guard subtitle.src.isValid == true else { - print("The provided subtitle source \(subtitle.src.absoluteString) is invalid") - return false - } - return true - } self.parseManifest() - self.appendSubtitlesLines(subtitles: validSubtitles) - if !self.extXMediaSubtitlesOriginallyExists, - validSubtitles.isEmpty { - // when the original m3u8 does not have any subtitles, and there are no valid subtitles to sideload - // then we remove the added subs attribute from the ExtXStreamInf entries - for (i, ele) in self.constructedManifestArray.enumerated() { - let line = HLSLine(lineString: ele) - line.paramsObject.removeValue(forKey: HLSKeywords.subtitles.rawValue) - self.constructedManifestArray[i] = line.joinLine() - } - } + self.appendSubtitlesLines(subtitles: subtitles) let constructed = self.constructedManifestArray.joined(separator: "\n") if THEOplayerConnectorSideloadedSubtitle.SHOW_DEBUG_LOGS { @@ -77,7 +59,7 @@ class MasterPlaylistParser: PlaylistParser { case HLSKeywords.subtitles.rawValue: line.paramsObject[HLSKeywords.groupId.rawValue] = "\"\(self.subtitlesGroupId)\"" self.lastMediaLine = self.constructedManifestArray.count - self.extXMediaSubtitlesOriginallyExists = true + default: break } diff --git a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Utils/SourceValidator.swift b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Utils/SourceValidator.swift index 3df4fa60..e327cdb5 100644 --- a/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Utils/SourceValidator.swift +++ b/Code/Sideloaded-TextTracks/Sources/THEOplayerConnectorSideloadedSubtitle/Utils/SourceValidator.swift @@ -15,7 +15,14 @@ class SourceValidator { return nil } - let filteredTextTracks = sideLoadedTextTracks.filter { $0.kind == .subtitles } + let subtitlesTextTracks = sideLoadedTextTracks.filter { $0.kind == .subtitles } + let filteredTextTracks = subtitlesTextTracks.filter { subtitle in + guard subtitle.src.isValid == true else { + print("[AVSubtitlesLoader] The provided subtitle source \(subtitle.src.absoluteString) is invalid.") + return false + } + return true + } if filteredTextTracks.isEmpty { print("[AVSubtitlesLoader] Unable to find a valid TextTrackDescription for sideloading.") return nil