@@ -10,94 +10,72 @@ import AVFoundation
1010import THEOplayerSDK
1111
1212class AVSubtitlesLoader : NSObject {
13+ private static var instances : [ AVSubtitlesLoader ] = [ ]
14+ static func addInstance( _ loader: AVSubtitlesLoader ) { Self . instances. append ( loader) }
15+ static func removeInstance( by id: String ) {
16+ Self . instances. removeAll { $0. _id == id }
17+ }
18+
1319 private let subtitles : [ TextTrackDescription ]
14- private( set) var variantTotalDuration : Double = 0
1520 private let transformer = SubtitlesTransformer ( )
1621 private let synchronizer : SubtitlesSynchronizer ?
22+ private let _id : String
23+ private var variantTotalDuration : Double = 0
1724
18- init ( subtitles: [ TextTrackDescription ] , player: THEOplayer ? ) {
25+ init ( subtitles: [ TextTrackDescription ] , id : String , player: THEOplayer ? = nil , cachingTask : CachingTask ? = nil ) {
1926 self . subtitles = subtitles
27+ self . _id = id
2028 self . synchronizer = SubtitlesSynchronizer ( player: player)
2129 self . synchronizer? . delegate = self . transformer
30+
31+ super. init ( )
32+
33+ _ = player? . addEventListener ( type: PlayerEventTypes . DESTROY, listener: { [ weak self] destroyEvent in self ? . handleDestroyEvent ( ) } )
34+ _ = cachingTask? . addEventListener ( type: CachingTaskEventTypes . STATE_CHANGE, listener: { [ weak self] cachingTaskStateChangeEvent in self ? . handleCachingTaskStateChangeEvent ( task: cachingTask) } )
2235 }
23-
24- func handleMasterManifestRequest( _ request: AVAssetResourceLoadingRequest ) -> Bool {
25- guard let originalURL = request. request. url? . withScheme ( newScheme: URLScheme . https) else {
26- return false
27- }
28-
29- MasterPlaylistParser ( url: originalURL) . sideLoadSubtitles ( subtitles: subtitles) { data in
30- guard let masterManifestData = data else {
31- print ( " [AVSubtitlesLoader] ERROR: Couldn't find manifest data " )
32- request. finishLoading ( with: URLError ( URLError . cannotParseResponse) )
33- return
34- }
35- let response = HTTPURLResponse ( url: originalURL, statusCode: 200 , httpVersion: nil , headerFields: nil )
36- request. response = response
37- request. dataRequest? . respond ( with: masterManifestData)
38- request. finishLoading ( )
36+
37+ func handleMasterManifestRequest( _ url: URL ) async -> Data ? {
38+ let parser = MasterPlaylistParser ( url: url)
39+
40+ guard let responseData = await parser. sideLoadSubtitles ( subtitles: subtitles) else {
41+ print ( " [AVSubtitlesLoader] ERROR: Couldn't find manifest data " )
42+ return nil
3943 }
40- return true
44+
45+ return responseData
4146 }
4247
43- func handleVariantManifest( _ request: AVAssetResourceLoadingRequest ) -> Bool {
44- guard let customSchemeURL = request. request. url,
45- let originalURLString = customSchemeURL. absoluteString. byRemovingScheme ( scheme: URLScheme . variantm3u8) ,
46- let originalURL = URL ( string: originalURLString) else {
47- print ( " [AVSubtitlesLoader] ERROR: Variant manifest is invalid " )
48- request. finishLoading ( with: URLError ( URLError . unsupportedURL) )
49- return false
50- }
51-
52- VariantPlaylistParser ( url: originalURL) . parse { playlist in
53- guard let playlist = playlist, let responseData = playlist. manifestData else {
54- print ( " [AVSubtitlesLoader] ERROR: Couldn't find variant data " )
55- request. finishLoading ( with: URLError ( URLError . cannotParseResponse) )
56- return
57- }
58- self . variantTotalDuration = playlist. totalPlayListDuration
59- let response = HTTPURLResponse ( url: originalURL, statusCode: 200 , httpVersion: nil , headerFields: nil )
60- request. response = response
61- request. dataRequest? . respond ( with: responseData)
62- request. finishLoading ( )
48+ func handleVariantManifest( _ url: URL ) async -> Data ? {
49+ let parser = VariantPlaylistParser ( url: url)
50+
51+ guard let playlist = await parser. parse ( ) ,
52+ let responseData = playlist. manifestData else {
53+ print ( " [AVSubtitlesLoader] ERROR: Couldn't find variant data " )
54+ return nil
6355 }
64- return true
56+
57+ self . variantTotalDuration = playlist. totalPlayListDuration
58+ return responseData
6559 }
66-
67- func handleSubtitles( _ request: AVAssetResourceLoadingRequest ) -> Bool {
68- guard let customSchemeURL = request. request. url else {
69- return false
70- }
71-
72- guard let originalURLString = customSchemeURL. absoluteString. byRemovingScheme ( scheme: URLScheme . subtitlesm3u8) ,
73- let originalURL = URL ( string: originalURLString) else {
74- print ( " [AVSubtitlesLoader] ERROR: Failed to revert subtitle URL! " )
75- return false
60+
61+ func handleSubtitles( _ url: URL ) -> Data ? {
62+ guard let trackDescription: THEOplayerSDK . TextTrackDescription = self . findTrackDescription ( by: url) else {
63+ return nil
7664 }
77-
78- let subtitlem3u8 = self . getSubtitleManifest ( for: originalURL )
65+
66+ let subtitlem3u8 = self . getSubtitleManifest ( for: url , trackDescription : trackDescription )
7967
8068 if THEOplayerConnectorSideloadedSubtitle . SHOW_DEBUG_LOGS {
8169 print ( " [AVSubtitlesLoader] SUBTITLE: +++++++ " )
8270 print ( subtitlem3u8)
8371 print ( " [AVSubtitlesLoader] SUBTITLE: ------ " )
8472 }
85-
86- guard let data = subtitlem3u8. data ( using: . utf8) else {
87- return false
88- }
89-
90- let response = HTTPURLResponse ( url: originalURL, statusCode: 200 , httpVersion: nil , headerFields: nil )
91- request. response = response
92- request. dataRequest? . respond ( with: data)
93- request. finishLoading ( )
94-
95- return true
73+
74+ return subtitlem3u8. data ( using: . utf8)
9675 }
9776
98- fileprivate func getSubtitleManifest( for originalURL: URL ) -> String {
99- let trackDescription : THEOplayerSDK . TextTrackDescription ? = self . findTrackDescription ( by: originalURL)
100- let format : THEOplayerSDK . TextTrackFormat = trackDescription? . format ?? . WebVTT
77+ fileprivate func getSubtitleManifest( for originalURL: URL , trackDescription: THEOplayerSDK . TextTrackDescription ) -> String {
78+ let format : THEOplayerSDK . TextTrackFormat = trackDescription. format ?? . WebVTT
10179 let timestamp : SSTextTrackDescription . WebVttTimestamp ? = ( trackDescription as? SSTextTrackDescription ) ? . vttTimestamp
10280 let autosync : Bool ? = ( trackDescription as? SSTextTrackDescription ) ? . automaticTimestampSyncEnabled
10381 let subtitlesMediaURL : String
@@ -132,6 +110,16 @@ class AVSubtitlesLoader: NSObject {
132110 }
133111 return track
134112 }
113+
114+ private func handleDestroyEvent( ) {
115+ Self . removeInstance ( by: _id)
116+ }
117+
118+ private func handleCachingTaskStateChangeEvent( task: CachingTask ? ) {
119+ guard let task,
120+ task. status == . evicted else { return }
121+ Self . removeInstance ( by: task. id)
122+ }
135123}
136124
137125enum URLScheme : String {
@@ -151,48 +139,40 @@ enum URLScheme: String {
151139 }
152140}
153141
154- extension AVSubtitlesLoader : ManifestInterceptor {
155- var customScheme : String {
156- //the initial interception scheme
157- URLScheme . masterm3u8. urlScheme
158- }
159-
160- func resourceLoader( _ resourceLoader: AVAssetResourceLoader , shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest ) -> Bool {
142+ extension AVSubtitlesLoader : MediaPlaylistInterceptor {
143+ func shouldInterceptPlaylistRequest( type: HlsPlaylistType ) -> Bool { false }
144+ func didInterceptPlaylistRequest( type: HlsPlaylistType , request: URLRequest ) async throws -> URLRequest { request }
145+
146+ func failedToPerformURLRequest( request: URLRequest , response: URLResponse ) {
161147 if THEOplayerConnectorSideloadedSubtitle . SHOW_DEBUG_LOGS {
162- print ( " [AVSubtitlesLoader] loadingRequest " , loadingRequest . request. url? . absoluteString ?? " " )
148+ print ( " [AVSubtitlesLoader] failedToPerformURLRequest " , request. url? . absoluteString ?? " " )
163149 }
164- return intercept ( loadingRequest: loadingRequest)
165-
166150 }
167-
168- func resourceLoader( _ resourceLoader: AVAssetResourceLoader , shouldWaitForRenewalOfRequestedResource renewalRequest: AVAssetResourceRenewalRequest ) -> Bool {
151+
152+ func shouldInterceptPlaylistResponse( type: HlsPlaylistType ) -> Bool { true }
153+ func didInterceptPlaylistResponse( type: HlsPlaylistType , url: URL , response: URLResponse , data: Data ) async throws -> Data {
169154 if THEOplayerConnectorSideloadedSubtitle . SHOW_DEBUG_LOGS {
170- print ( " [AVSubtitlesLoader] renewalRequest " , renewalRequest . request . url? . absoluteString ?? " " )
155+ print ( " [AVSubtitlesLoader] intercept url " , url. absoluteString, self )
171156 }
172- return intercept ( loadingRequest : renewalRequest )
157+ return await interceptResponse ( type : type , url : url , data : data )
173158 }
174-
175- private func intercept( loadingRequest: AVAssetResourceLoadingRequest ) -> Bool {
176- guard let scheme = loadingRequest. request. url? . scheme else {
177- return false
178- }
179- switch scheme {
180- case URLScheme . masterm3u8. name:
159+
160+ private func interceptResponse( type: HlsPlaylistType , url: URL , data: Data ) async -> Data {
161+ switch type {
162+ case . master :
181163 // intercept the master manifest to append the subtitles
182- return self . handleMasterManifestRequest ( loadingRequest )
183- case URLScheme . variantm3u8 . name :
164+ return await self . handleMasterManifestRequest ( url ) ?? data
165+ case . video :
184166 // intercept the variant manifest to get the duration
185- return self . handleVariantManifest ( loadingRequest )
186- case URLScheme . subtitlesm3u8 . name :
167+ return await self . handleVariantManifest ( url ) ?? data
168+ case . subtitles :
187169 // intercept the subtitle request to respond with the HLS subtitle
188- return self . handleSubtitles ( loadingRequest )
170+ return self . handleSubtitles ( url ) ?? data
189171 default :
190172 break
191173 }
192-
193- return false
174+ return data
194175 }
195-
196176}
197177
198178extension THEOplayer {
@@ -202,17 +182,18 @@ extension THEOplayer {
202182 - Remark:
203183 - Once used this method, always use it to set a source (even if there are no sideloaded subtitles in it), otherwise the subtitle helper logic can break the playback behavior
204184 */
205- public func setSourceWithSubtitles( source: SourceDescription ? ) {
206-
207- if let source = source {
208- if let sideLoadedTextTracks = SourceValidator . getValidTextTracks ( source) {
209- let subtitleLoader = AVSubtitlesLoader ( subtitles: sideLoadedTextTracks, player: self )
210- self . developerSettings? . manifestInterceptor = subtitleLoader
211- } else {
212- self . developerSettings? . manifestInterceptor = nil
213- }
185+ public func setSourceWithSubtitles( source: SourceDescription ? ) {
186+ if let source = source,
187+ let sideLoadedTextTracks = SourceValidator . getValidTextTracks ( source) {
188+ let loader = AVSubtitlesLoader (
189+ subtitles: sideLoadedTextTracks,
190+ id: String ( self . uid) ,
191+ player: self
192+ )
193+ AVSubtitlesLoader . addInstance ( loader)
194+ self . network. addMediaPlaylistInterceptor ( loader)
214195 } else {
215- self . developerSettings ? . manifestInterceptor = nil
196+ AVSubtitlesLoader . removeInstance ( by : String ( self . uid ) )
216197 }
217198
218199 self . source = source
@@ -228,14 +209,18 @@ extension Cache {
228209 - Once used this method, always use it to cache a source (even if there are no sideloaded subtitles in it), otherwise the subtitle helper logic can break the caching behavior
229210 */
230211 public func createTaskWithSubtitles( source: SourceDescription , parameters: CachingParameters ? ) -> CachingTask ? {
212+ guard let cachingTask = createTask ( source: source, parameters: parameters) else { return nil }
231213 if let sideLoadedTextTracks = SourceValidator . getValidTextTracks ( source) {
232- let subtitleLoader = AVSubtitlesLoader ( subtitles: sideLoadedTextTracks, player: nil )
233- self . developerSettings? . manifestInterceptor = subtitleLoader
234- } else {
235- self . developerSettings? . manifestInterceptor = nil
214+ let loader = AVSubtitlesLoader (
215+ subtitles: sideLoadedTextTracks,
216+ id: cachingTask. id,
217+ cachingTask: cachingTask
218+ )
219+ AVSubtitlesLoader . addInstance ( loader)
220+ cachingTask. network. addMediaPlaylistInterceptor ( loader)
236221 }
237222
238- return createTask ( source : source , parameters : parameters )
223+ return cachingTask
239224 }
240225}
241226#endif
0 commit comments