@@ -85,6 +85,7 @@ public final class TPStreamsDownloadManager {
8585
8686 let localOfflineAsset = LocalOfflineAsset . create (
8787 assetId: asset. id,
88+ videoId: asset. video? . id,
8889 srcURL: asset. video!. playbackURL,
8990 title: asset. title,
9091 resolution: videoQuality. resolution,
@@ -102,42 +103,64 @@ public final class TPStreamsDownloadManager {
102103 tpStreamsDownloadDelegate? . onStart ( offlineAsset: localOfflineAsset. asOfflineAsset ( ) )
103104 tpStreamsDownloadDelegate? . onStateChange ( status: . inProgress, offlineAsset: localOfflineAsset. asOfflineAsset ( ) )
104105
105- if ( asset. video? . drmEncrypted == true ) {
106+ if asset. video? . drmEncrypted == true {
106107 M3U8Parser . extractContentID ( url: URL ( string: asset. video!. playbackURL) !) { result in
107108 switch result {
108109 case . success( let drmContentId) :
109- print ( " Extracted DRM content ID: \( drmContentId) " )
110110 DispatchQueue . main. async {
111111 LocalOfflineAsset . manager. update ( id: asset. id, with: [ " drmContentId " : drmContentId] )
112112 self . requestPersistentKey ( localOfflineAsset. assetId)
113113 }
114- case . failure( let error ) :
115- print ( " Error extracting content ID: \( error . localizedDescription ) " )
114+ case . failure:
115+ break
116116 }
117117 }
118118 }
119119 ToastHelper . show ( message: DownloadMessages . started)
120120 }
121121
122122 private func preFetchEncryptionKey( for video: Video , assetId: String , accessToken: String ? ) {
123- guard let videoId = video. id else {
124- return
125- }
126-
127- ContentProtectionAPI . fetchEncryptionKey ( videoId: videoId, accessToken: accessToken) { [ weak self] keyData in
128- if let keyData = keyData {
129- self ? . saveEncryptionKey ( keyData, videoId: videoId)
123+ guard let videoId = video. id, !videoId. isEmpty else { return }
124+ guard let url = URL ( string: video. playbackURL) else { return }
125+ parsePlaylist ( url, videoId, accessToken)
126+ }
127+
128+ private func parsePlaylist( _ playlistURL: URL , _ videoId: String , _ accessToken: String ? ) {
129+ URLSession . shared. dataTask ( with: playlistURL) { [ weak self] responseData, _, _ in
130+ guard let responseData = responseData, let playlistString = String ( data: responseData, encoding: . utf8) else {
131+ ContentProtectionAPI . fetchEncryptionKey ( videoId: videoId, accessToken: accessToken) { [ weak self] encryptionKeyData in
132+ if let encryptionKeyData = encryptionKeyData {
133+ self ? . encryptionKeyRepository. save ( encryptionKey: encryptionKeyData, for: videoId)
134+ }
135+ }
136+ return
130137 }
131- }
138+ if let masterPlaylistPath = playlistString. components ( separatedBy: . newlines) . first ( where: { $0. hasSuffix ( " .m3u8 " ) } ) , let masterPlaylistURL = URL ( string: masterPlaylistPath, relativeTo: playlistURL) {
139+ self ? . parsePlaylist ( masterPlaylistURL, videoId, accessToken)
140+
141+ } else {
142+ let encryptionKeyPattern = " #EXT-X-KEY:.*URI= \" ([^ \" ]+) \" " , regexMatch = try ? NSRegularExpression ( pattern: encryptionKeyPattern) . firstMatch ( in: playlistString, range: NSRange ( playlistString. startIndex... , in: playlistString) )
143+ if let regexMatch = regexMatch, let matchRange = Range ( regexMatch. range ( at: 1 ) , in: playlistString) , let keyURL = URL ( string: String ( playlistString [ matchRange] ) , relativeTo: playlistURL) {
144+ ContentProtectionAPI . fetchEncryptionKey ( url: keyURL, accessToken: accessToken) { [ weak self] encryptionKeyData in
145+ if let encryptionKeyData = encryptionKeyData { [ keyURL. absoluteString. components ( separatedBy: " ? " ) . first, keyURL. pathComponents. last, videoId] . compactMap { $0 } . forEach { self ? . encryptionKeyRepository. save ( encryptionKey: encryptionKeyData, for: $0) } }
146+ }
147+ } else {
148+ ContentProtectionAPI . fetchEncryptionKey ( videoId: videoId, accessToken: accessToken) { [ weak self] encryptionKeyData in
149+ if let encryptionKeyData = encryptionKeyData {
150+ self ? . encryptionKeyRepository. save ( encryptionKey: encryptionKeyData, for: videoId)
151+ }
152+ }
153+ }
154+ }
155+ } . resume ( )
132156 }
133157
134- private func saveEncryptionKey( _ keyData: Data , videoId : String ) {
135- encryptionKeyRepository. save ( encryptionKey: keyData, for: videoId )
158+ private func saveEncryptionKey( _ keyData: Data , identifier : String ) {
159+ encryptionKeyRepository. save ( encryptionKey: keyData, for: identifier )
136160 }
137161
138162 private func requestPersistentKey( _ assetID: String ) {
139163 guard let localOfflineAsset = LocalOfflineAsset . manager. get ( id: assetID) else {
140- print ( " Asset with ID \( assetID) does not exist. " )
141164 return
142165 }
143166 contentKeySession. processContentKeyRequest (
@@ -149,7 +172,6 @@ public final class TPStreamsDownloadManager {
149172
150173 public func pauseDownload( _ assetId: String ) {
151174 guard let localOfflineAsset = LocalOfflineAsset . manager. get ( id: assetId) else {
152- print ( " Asset with ID \( assetId) does not exist. " )
153175 return
154176 }
155177
@@ -164,7 +186,6 @@ public final class TPStreamsDownloadManager {
164186
165187 public func resumeDownload( _ assetId: String ) {
166188 guard let localOfflineAsset = LocalOfflineAsset . manager. get ( id: assetId) else {
167- print ( " Asset with ID \( assetId) does not exist. " )
168189 return
169190 }
170191
@@ -181,7 +202,6 @@ public final class TPStreamsDownloadManager {
181202
182203 public func cancelDownload( _ assetId: String ) {
183204 guard let localOfflineAsset = LocalOfflineAsset . manager. get ( id: assetId) else {
184- print ( " Asset with ID \( assetId) does not exist. " )
185205 return
186206 }
187207
@@ -262,12 +282,10 @@ public final class TPStreamsDownloadManager {
262282 if let videoId = localOfflineAsset. videoId {
263283 encryptionKeyRepository. delete ( for: videoId)
264284 }
285+ encryptionKeyRepository. delete ( for: localOfflineAsset. assetId)
265286 }
266287
267288 public func getAllOfflineAssets( ) -> [ OfflineAsset ] {
268- // This method retrieves all offline assets from the local storage.
269- // It filters out any assets that have a status of 'deleted',
270- // ensuring that only available (non-deleted) video assets are returned.
271289 return LocalOfflineAsset . manager. getAll ( )
272290 . filter { $0. status != Status . deleted. rawValue }
273291 . map { $0. asOfflineAsset ( ) }
0 commit comments