MediaLibraryService.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. /*****************************************************************************
  2. * MediaLibraryService.swift
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright © 2018 VideoLAN. All rights reserved.
  6. * Copyright © 2018 Videolabs
  7. *
  8. * Authors: Soomin Lee <bubu # mikan.io>
  9. *
  10. * Refer to the COPYING file of the official project for license.
  11. *****************************************************************************/
  12. // MARK: - Notification names
  13. extension Notification.Name {
  14. static let VLCNewFileAddedNotification = Notification.Name("NewFileAddedNotification")
  15. }
  16. // For objc
  17. extension NSNotification {
  18. @objc static let VLCNewFileAddedNotification = Notification.Name.VLCNewFileAddedNotification
  19. }
  20. // MARK: -
  21. @objc protocol MediaLibraryObserver: class {
  22. // Video
  23. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  24. didAddVideos videos: [VLCMLMedia])
  25. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  26. didModifyVideos videos: [VLCMLMedia])
  27. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  28. didDeleteMediaWithIds ids: [NSNumber])
  29. // ShowEpisodes
  30. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  31. didAddShowEpisodes showEpisodes: [VLCMLMedia])
  32. // Tumbnail
  33. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  34. thumbnailReady media: VLCMLMedia,
  35. type: VLCMLThumbnailSizeType, success: Bool)
  36. // Tracks
  37. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  38. didAddTracks tracks: [VLCMLMedia])
  39. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  40. didModifyTracks tracks: [VLCMLMedia])
  41. // Artists
  42. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  43. didAddArtists artists: [VLCMLArtist])
  44. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  45. didModifyArtistsWithIds artistsIds: [NSNumber])
  46. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  47. didDeleteArtistsWithIds artistsIds: [NSNumber])
  48. // Albums
  49. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  50. didAddAlbums albums: [VLCMLAlbum])
  51. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  52. didModifyAlbumsWithIds albumsIds: [NSNumber])
  53. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  54. didDeleteAlbumsWithIds albumsIds: [NSNumber])
  55. // AlbumTracks
  56. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  57. didAddAlbumTracks albumTracks: [VLCMLMedia])
  58. // Genres
  59. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  60. didAddGenres genres: [VLCMLGenre])
  61. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  62. didModifyGenresWithIds genresIds: [NSNumber])
  63. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  64. didDeleteGenresWithIds genresIds: [NSNumber])
  65. // Playlist
  66. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  67. didAddPlaylists playlists: [VLCMLPlaylist])
  68. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  69. didModifyPlaylistsWithIds playlistsIds: [NSNumber])
  70. @objc optional func medialibrary(_ medialibrary: MediaLibraryService,
  71. didDeletePlaylistsWithIds playlistsIds: [NSNumber])
  72. // Force Rescan
  73. @objc optional func medialibraryDidStartRescan()
  74. }
  75. // MARK: -
  76. protocol MediaLibraryMigrationDelegate: class {
  77. func medialibraryDidStartMigration(_ medialibrary: MediaLibraryService)
  78. func medialibraryDidFinishMigration(_ medialibrary: MediaLibraryService)
  79. func medialibraryDidStopMigration(_ medialibrary: MediaLibraryService)
  80. }
  81. // MARK: -
  82. class MediaLibraryService: NSObject {
  83. private static let databaseName: String = "medialibrary.db"
  84. private static let migrationKey: String = "MigratedToVLCMediaLibraryKit"
  85. private static let didForceRescan: String = "MediaLibraryDidForceRescan"
  86. private var didMigrate = UserDefaults.standard.bool(forKey: MediaLibraryService.migrationKey)
  87. private var didFinishDiscovery = false
  88. // Using ObjectIdentifier to avoid duplication and facilitate
  89. // identification of observing object
  90. private var observers = [ObjectIdentifier: Observer]()
  91. private(set) lazy var medialib = VLCMediaLibrary()
  92. weak var migrationDelegate: MediaLibraryMigrationDelegate?
  93. override init() {
  94. super.init()
  95. medialib.delegate = self
  96. setupMediaLibrary()
  97. NotificationCenter.default.addObserver(self, selector: #selector(reload),
  98. name: .VLCNewFileAddedNotification, object: nil)
  99. NotificationCenter.default.addObserver(self, selector: #selector(handleWillEnterForegroundNotification),
  100. name: UIApplication.willEnterForegroundNotification, object: nil)
  101. }
  102. }
  103. // MARK: - Private initializers
  104. private extension MediaLibraryService {
  105. private func setupMediaDiscovery(at path: String) {
  106. let mediaFileDiscoverer = VLCMediaFileDiscoverer.sharedInstance()
  107. mediaFileDiscoverer?.directoryPath = path
  108. mediaFileDiscoverer?.addObserver(self)
  109. mediaFileDiscoverer?.startDiscovering()
  110. }
  111. private func startMediaLibrary(on path: String) {
  112. guard medialib.start() else {
  113. assertionFailure("MediaLibraryService: Medialibrary failed to start.")
  114. return
  115. }
  116. if UserDefaults.standard.bool(forKey: MediaLibraryService.didForceRescan) == false {
  117. medialib.forceRescan()
  118. UserDefaults.standard.set(true, forKey: MediaLibraryService.didForceRescan)
  119. }
  120. /* exclude Document directory from backup (QA1719) */
  121. if let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
  122. var excludeURL = URL(fileURLWithPath: documentPath)
  123. var resourceValue = URLResourceValues()
  124. let excludeMediaLibrary = !UserDefaults.standard.bool(forKey: kVLCSettingBackupMediaLibrary)
  125. resourceValue.isExcludedFromBackup = excludeMediaLibrary
  126. do {
  127. try excludeURL.setResourceValues(resourceValue)
  128. } catch let error {
  129. assertionFailure("MediaLibraryService: start: \(error.localizedDescription)")
  130. }
  131. }
  132. medialib.reload()
  133. medialib.discover(onEntryPoint: "file://" + path)
  134. }
  135. private func setupMediaLibrary() {
  136. guard let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first,
  137. let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first else {
  138. preconditionFailure("MediaLibraryService: Unable to init medialibrary.")
  139. }
  140. setupMediaDiscovery(at: documentPath)
  141. let databasePath = libraryPath + "/MediaLibrary/" + MediaLibraryService.databaseName
  142. let thumbnailPath = libraryPath + "/MediaLibrary/Thumbnails"
  143. let medialibraryPath = libraryPath + "/MediaLibrary/Internal"
  144. _ = try? FileManager.default.removeItem(atPath: thumbnailPath)
  145. do {
  146. try FileManager.default.createDirectory(atPath: medialibraryPath,
  147. withIntermediateDirectories: true)
  148. } catch let error as NSError {
  149. assertionFailure("Failed to create directory: \(error.localizedDescription)")
  150. }
  151. let medialibraryStatus = medialib.setupMediaLibrary(databasePath: databasePath,
  152. medialibraryPath: medialibraryPath)
  153. switch medialibraryStatus {
  154. case .success, .dbReset:
  155. startMediaLibrary(on: documentPath)
  156. case .alreadyInitialized:
  157. assertionFailure("MediaLibraryService: Medialibrary already initialized.")
  158. case .failed:
  159. preconditionFailure("MediaLibraryService: Failed to setup medialibrary.")
  160. case .dbCorrupted:
  161. medialib.clearDatabase(restorePlaylists: true)
  162. startMediaLibrary(on: documentPath)
  163. @unknown default:
  164. assertionFailure("MediaLibraryService: unhandled case")
  165. }
  166. }
  167. }
  168. // MARK: - Migration
  169. private extension MediaLibraryService {
  170. func startMigrationIfNeeded() {
  171. guard !didMigrate else {
  172. return
  173. }
  174. migrationDelegate?.medialibraryDidStartMigration(self)
  175. migrateToNewMediaLibrary() {
  176. [unowned self] success in
  177. if success {
  178. self.migrationDelegate?.medialibraryDidFinishMigration(self)
  179. } else {
  180. self.migrationDelegate?.medialibraryDidStopMigration(self)
  181. }
  182. }
  183. }
  184. func migrateMedia(_ oldMedialibrary: MLMediaLibrary,
  185. completionHandler: @escaping (Bool) -> Void) {
  186. guard let allFiles = MLFile.allFiles() as? [MLFile] else {
  187. assertionFailure("MediaLibraryService: Migration: Unable to retrieve all files")
  188. completionHandler(false)
  189. return
  190. }
  191. for media in allFiles {
  192. if let newMedia = fetchMedia(with: media.url) {
  193. newMedia.updateTitle(media.title)
  194. newMedia.setPlayCount(media.playCount.uint32Value)
  195. newMedia.setMetadataOf(.progress, intValue: media.lastPosition.int64Value)
  196. newMedia.setMetadataOf(.seen, intValue: media.unread.int64Value)
  197. // Only delete files that are not in playlist
  198. if media.labels.isEmpty {
  199. oldMedialibrary.remove(media)
  200. }
  201. }
  202. }
  203. oldMedialibrary.save {
  204. success in
  205. completionHandler(success)
  206. }
  207. }
  208. // This private method migrates old playlist and removes file and playlist
  209. // from the old medialibrary.
  210. // Note: This removes **only** files that are in a playlist
  211. func migratePlaylists(_ oldMedialibrary: MLMediaLibrary,
  212. completionHandler: @escaping (Bool) -> Void) {
  213. guard let allLabels = MLLabel.allLabels() as? [MLLabel] else {
  214. assertionFailure("MediaLibraryService: Migration: Unable to retrieve all labels")
  215. completionHandler(false)
  216. return
  217. }
  218. for label in allLabels {
  219. guard let newPlaylist = createPlaylist(with: label.name) else {
  220. assertionFailure("MediaLibraryService: Migration: Unable to create playlist.")
  221. continue
  222. }
  223. guard let files = label.files as? Set<MLFile> else {
  224. assertionFailure("MediaLibraryService: Migration: Unable to retrieve files from label")
  225. oldMedialibrary.remove(label)
  226. continue
  227. }
  228. for file in files {
  229. if let newMedia = fetchMedia(with: file.url) {
  230. if newPlaylist.appendMedia(withIdentifier: newMedia.identifier()) {
  231. oldMedialibrary.remove(file)
  232. }
  233. }
  234. }
  235. oldMedialibrary.remove(label)
  236. }
  237. oldMedialibrary.save() {
  238. success in
  239. completionHandler(success)
  240. }
  241. }
  242. func migrateToNewMediaLibrary(completionHandler: @escaping (Bool) -> Void) {
  243. guard let oldMedialibrary = MLMediaLibrary.sharedMediaLibrary() as? MLMediaLibrary else {
  244. assertionFailure("MediaLibraryService: Migration: Unable to retrieve old medialibrary")
  245. completionHandler(false)
  246. return
  247. }
  248. migrateMedia(oldMedialibrary) {
  249. [unowned self] success in
  250. guard success else {
  251. assertionFailure("MediaLibraryService: Failed to migrate Media.")
  252. completionHandler(false)
  253. return
  254. }
  255. self.migratePlaylists(oldMedialibrary) {
  256. [unowned self] success in
  257. if success {
  258. UserDefaults.standard.set(true, forKey: MediaLibraryService.migrationKey)
  259. self.didMigrate = true
  260. } else {
  261. assertionFailure("MediaLibraryService: Failed to migrate Playlist.")
  262. }
  263. completionHandler(success)
  264. }
  265. }
  266. }
  267. }
  268. // MARK: - Observer
  269. private extension MediaLibraryService {
  270. struct Observer {
  271. weak var observer: MediaLibraryObserver?
  272. }
  273. }
  274. extension MediaLibraryService {
  275. func addObserver(_ observer: MediaLibraryObserver) {
  276. let identifier = ObjectIdentifier(observer)
  277. observers[identifier] = Observer(observer: observer)
  278. }
  279. func removeObserver(_ observer: MediaLibraryObserver) {
  280. let identifier = ObjectIdentifier(observer)
  281. observers.removeValue(forKey: identifier)
  282. }
  283. }
  284. // MARK: - Helpers
  285. @objc extension MediaLibraryService {
  286. @objc func reload() {
  287. medialib.reload()
  288. }
  289. @objc func forceRescan() {
  290. medialib.forceRescan()
  291. }
  292. @objc func reindexAllMediaForSpotlight() {
  293. media(ofType: .video).forEach { $0.updateCoreSpotlightEntry() }
  294. media(ofType: .audio).forEach { $0.updateCoreSpotlightEntry() }
  295. }
  296. /// Returns number of *ALL* files(audio and video) present in the medialibrary database
  297. func numberOfFiles() -> Int {
  298. return (medialib.audioFiles()?.count ?? 0) + (medialib.videoFiles()?.count ?? 0)
  299. }
  300. /// Returns *ALL* file found for a specified VLCMLMediaType
  301. ///
  302. /// - Parameter type: Type of the media
  303. /// - Returns: Array of VLCMLMedia
  304. func media(ofType type: VLCMLMediaType,
  305. sortingCriteria sort: VLCMLSortingCriteria = .alpha,
  306. desc: Bool = false) -> [VLCMLMedia] {
  307. return type == .video ? medialib.videoFiles(with: sort, desc: desc) ?? []
  308. : medialib.audioFiles(with: sort, desc: desc) ?? []
  309. }
  310. @objc func fetchMedia(with mrl: URL?) -> VLCMLMedia? {
  311. guard let mrl = mrl else {
  312. return nil //Happens when we have a URL or there is no currently playing file
  313. }
  314. return medialib.media(withMrl: mrl)
  315. }
  316. @objc func media(for identifier: VLCMLIdentifier) -> VLCMLMedia? {
  317. return medialib.media(withIdentifier: identifier)
  318. }
  319. func savePlaybackState(from player: PlaybackService) {
  320. let media: VLCMedia? = player.currentlyPlayingMedia
  321. guard let mlMedia = fetchMedia(with: media?.url.absoluteURL) else {
  322. // we opened a url and not a local file
  323. return
  324. }
  325. mlMedia.isNew = false
  326. mlMedia.progress = player.playbackPosition
  327. mlMedia.audioTrackIndex = Int64(player.indexOfCurrentAudioTrack)
  328. mlMedia.subtitleTrackIndex = Int64(player.indexOfCurrentSubtitleTrack)
  329. mlMedia.chapterIndex = Int64(player.indexOfCurrentChapter)
  330. mlMedia.titleIndex = Int64(player.indexOfCurrentTitle)
  331. if mlMedia.type() == .video {
  332. mlMedia.requestThumbnail(of: .thumbnail, desiredWidth: 320,
  333. desiredHeight: 200, atPosition: player.playbackPosition)
  334. }
  335. }
  336. }
  337. // MARK: - Application notifications
  338. @objc private extension MediaLibraryService {
  339. @objc private func handleWillEnterForegroundNotification() {
  340. guard let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
  341. assertionFailure("MediaLibraryService: handleWillEnterForegroundNotification: Failed to retrieve documentPath")
  342. return
  343. }
  344. // On each foreground notification we check if there is a `.Trash` folder which is invisible
  345. // for the user that can be created by deleting media from the Files app.
  346. // This could lead to disk space issues.
  347. // For now since we do not handle restoration, we delete the `.Trash` folder every time.
  348. _ = try? FileManager.default.removeItem(atPath: documentPath + "/.Trash")
  349. // Reload in order to make sure that there is no old artifacts left
  350. reload()
  351. }
  352. }
  353. // MARK: - Audio methods
  354. @objc extension MediaLibraryService {
  355. func artists(sortingCriteria sort: VLCMLSortingCriteria = .alpha,
  356. desc: Bool = false, listAll all: Bool = false) -> [VLCMLArtist] {
  357. return medialib.artists(with: sort, desc: desc, all: all) ?? []
  358. }
  359. func albums(sortingCriteria sort: VLCMLSortingCriteria = .alpha,
  360. desc: Bool = false) -> [VLCMLAlbum] {
  361. return medialib.albums(with: sort, desc: desc) ?? []
  362. }
  363. }
  364. // MARK: - Video methods
  365. extension MediaLibraryService {
  366. func requestThumbnail(for media: VLCMLMedia) {
  367. let thumbnailStatus = media.isThumbnailGenerated()
  368. switch thumbnailStatus {
  369. case .missing, .failure:
  370. break
  371. case .available, .persistentFailure, .crash:
  372. return
  373. @unknown default:
  374. assertionFailure("MediaLibraryService: requestThumbnail: Unknown thumbnail status.")
  375. }
  376. if !media.requestThumbnail(of: .thumbnail, desiredWidth: 320, desiredHeight: 200, atPosition: 0.03) {
  377. assertionFailure("MediaLibraryService: Failed to generate thumbnail for: \(media.identifier())")
  378. }
  379. }
  380. func requestThumbnail(for media: [VLCMLMedia]) {
  381. media.forEach() {
  382. requestThumbnail(for: $0)
  383. }
  384. }
  385. }
  386. // MARK: - Playlist methods
  387. @objc extension MediaLibraryService {
  388. func createPlaylist(with name: String) -> VLCMLPlaylist? {
  389. return medialib.createPlaylist(withName: name)
  390. }
  391. func deletePlaylist(with identifier: VLCMLIdentifier) -> Bool {
  392. return medialib.deletePlaylist(withIdentifier: identifier)
  393. }
  394. func playlists(sortingCriteria sort: VLCMLSortingCriteria = .default,
  395. desc: Bool = false) -> [VLCMLPlaylist] {
  396. return medialib.playlists(with: sort, desc: desc) ?? []
  397. }
  398. }
  399. // MARK: - Genre methods
  400. extension MediaLibraryService {
  401. func genres(sortingCriteria sort: VLCMLSortingCriteria = .alpha,
  402. desc: Bool = false) -> [VLCMLGenre] {
  403. return medialib.genres(with: sort, desc: desc) ?? []
  404. }
  405. }
  406. // MARK: - VLCMediaFileDiscovererDelegate
  407. extension MediaLibraryService: VLCMediaFileDiscovererDelegate {
  408. func mediaFileAdded(_ filePath: String!, loading isLoading: Bool) {
  409. guard !isLoading else {
  410. return
  411. }
  412. reload()
  413. }
  414. func mediaFileDeleted(_ filePath: String!) {
  415. reload()
  416. }
  417. }
  418. // MARK: - VLCMediaLibraryDelegate - Media
  419. extension MediaLibraryService: VLCMediaLibraryDelegate {
  420. func medialibrary(_ medialibrary: VLCMediaLibrary, didAddMedia media: [VLCMLMedia]) {
  421. media.forEach { $0.updateCoreSpotlightEntry() }
  422. let videos = media.filter {( $0.type() == .video )}
  423. let tracks = media.filter {( $0.type() == .audio )}
  424. for observer in observers {
  425. observer.value.observer?.medialibrary?(self, didAddVideos: videos)
  426. observer.value.observer?.medialibrary?(self, didAddTracks: tracks)
  427. }
  428. }
  429. func medialibrary(_ medialibrary: VLCMediaLibrary, didModifyMediaWithIds mediaIds: [NSNumber]) {
  430. var media = [VLCMLMedia]()
  431. mediaIds.forEach() {
  432. guard let safeMedia = medialib.media(withIdentifier: $0.int64Value) else {
  433. return
  434. }
  435. media.append(safeMedia)
  436. }
  437. media.forEach { $0.updateCoreSpotlightEntry() }
  438. let showEpisodes = media.filter {( $0.subtype() == .showEpisode )}
  439. let albumTrack = media.filter {( $0.subtype() == .albumTrack )}
  440. let videos = media.filter {( $0.type() == .video)}
  441. let tracks = media.filter {( $0.type() == .audio)}
  442. // Shows and albumtracks are known only after when the medialibrary calls didModifyMedia
  443. for observer in observers {
  444. observer.value.observer?.medialibrary?(self, didAddShowEpisodes: showEpisodes)
  445. observer.value.observer?.medialibrary?(self, didAddAlbumTracks: albumTrack)
  446. observer.value.observer?.medialibrary?(self, didModifyVideos: videos)
  447. observer.value.observer?.medialibrary?(self, didModifyTracks: tracks)
  448. }
  449. }
  450. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteMediaWithIds mediaIds: [NSNumber]) {
  451. var stringIds = [String]()
  452. mediaIds.forEach { stringIds.append("\($0)") }
  453. CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: stringIds, completionHandler: nil)
  454. for observer in observers {
  455. observer.value.observer?.medialibrary?(self, didDeleteMediaWithIds: mediaIds)
  456. }
  457. }
  458. func medialibrary(_ medialibrary: VLCMediaLibrary, thumbnailReadyFor media: VLCMLMedia,
  459. of type: VLCMLThumbnailSizeType, withSuccess success: Bool) {
  460. for observer in observers {
  461. observer.value.observer?.medialibrary?(self, thumbnailReady: media,
  462. type: type, success: success)
  463. }
  464. }
  465. }
  466. // MARK: - VLCMediaLibraryDelegate - Artists
  467. extension MediaLibraryService {
  468. func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd artists: [VLCMLArtist]) {
  469. for observer in observers {
  470. observer.value.observer?.medialibrary?(self, didAddArtists: artists)
  471. }
  472. }
  473. func medialibrary(_ medialibrary: VLCMediaLibrary,
  474. didModifyArtistsWithIds artistsIds: [NSNumber]) {
  475. for observer in observers {
  476. observer.value.observer?.medialibrary?(self, didModifyArtistsWithIds: artistsIds)
  477. }
  478. }
  479. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteArtistsWithIds artistsIds: [NSNumber]) {
  480. for observer in observers {
  481. observer.value.observer?.medialibrary?(self, didDeleteArtistsWithIds: artistsIds)
  482. }
  483. }
  484. }
  485. // MARK: - VLCMediaLibraryDelegate - Albums
  486. extension MediaLibraryService {
  487. func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd albums: [VLCMLAlbum]) {
  488. for observer in observers {
  489. observer.value.observer?.medialibrary?(self, didAddAlbums: albums)
  490. }
  491. }
  492. func medialibrary(_ medialibrary: VLCMediaLibrary,
  493. didModifyAlbumsWithIds albumsIds: [NSNumber]) {
  494. for observer in observers {
  495. observer.value.observer?.medialibrary?(self, didModifyAlbumsWithIds: albumsIds)
  496. }
  497. }
  498. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteAlbumsWithIds albumsIds: [NSNumber]) {
  499. for observer in observers {
  500. observer.value.observer?.medialibrary?(self, didDeleteAlbumsWithIds: albumsIds)
  501. }
  502. }
  503. }
  504. // MARK: - VLCMediaLibraryDelegate - Genres
  505. extension MediaLibraryService {
  506. func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd genres: [VLCMLGenre]) {
  507. for observer in observers {
  508. observer.value.observer?.medialibrary?(self, didAddGenres: genres)
  509. }
  510. }
  511. func medialibrary(_ medialibrary: VLCMediaLibrary,
  512. didModifyGenresWithIds genresIds: [NSNumber]) {
  513. for observer in observers {
  514. observer.value.observer?.medialibrary?(self, didModifyGenresWithIds: genresIds)
  515. }
  516. }
  517. func medialibrary(_ medialibrary: VLCMediaLibrary,
  518. didDeleteGenresWithIds genresIds: [NSNumber]) {
  519. for observer in observers {
  520. observer.value.observer?.medialibrary?(self, didDeleteGenresWithIds: genresIds)
  521. }
  522. }
  523. }
  524. // MARK: - VLCMediaLibraryDelegate - Playlists
  525. extension MediaLibraryService {
  526. func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd playlists: [VLCMLPlaylist]) {
  527. for observer in observers {
  528. observer.value.observer?.medialibrary?(self, didAddPlaylists: playlists)
  529. }
  530. }
  531. func medialibrary(_ medialibrary: VLCMediaLibrary,
  532. didModifyPlaylistsWithIds playlistsIds: [NSNumber]) {
  533. for observer in observers {
  534. observer.value.observer?.medialibrary?(self, didModifyPlaylistsWithIds: playlistsIds)
  535. }
  536. }
  537. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeletePlaylistsWithIds playlistsIds: [NSNumber]) {
  538. for observer in observers {
  539. observer.value.observer?.medialibrary?(self, didDeletePlaylistsWithIds: playlistsIds)
  540. }
  541. }
  542. }
  543. // MARK: - VLCMediaLibraryDelegate - Discovery
  544. extension MediaLibraryService {
  545. func medialibrary(_ medialibrary: VLCMediaLibrary, didStartDiscovery entryPoint: String) {
  546. }
  547. func medialibrary(_ medialibrary: VLCMediaLibrary, didCompleteDiscovery entryPoint: String) {
  548. didFinishDiscovery = true
  549. }
  550. func medialibrary(_ medialibrary: VLCMediaLibrary, didProgressDiscovery entryPoint: String) {
  551. }
  552. func medialibrary(_ medialibrary: VLCMediaLibrary, didUpdateParsingStatsWithPercent percent: UInt32) {
  553. if didFinishDiscovery && percent == 100 {
  554. startMigrationIfNeeded()
  555. }
  556. }
  557. }
  558. // MARK: - VLCMediaLibraryDelegate - Exception handling
  559. extension MediaLibraryService {
  560. func medialibrary(_ medialibrary: VLCMediaLibrary,
  561. unhandledExceptionWithContext context: String,
  562. errorMessage: String, clearSuggested: Bool) -> Bool {
  563. if clearSuggested {
  564. medialib.clearDatabase(restorePlaylists: true)
  565. setupMediaLibrary()
  566. return true
  567. }
  568. return false
  569. }
  570. }
  571. // MARK: - VLCMLMediaLibraryDelegate - Force rescan
  572. extension MediaLibraryService {
  573. func medialibraryDidStartRescan(_ medialibrary: VLCMediaLibrary) {
  574. for observer in observers {
  575. observer.value.observer?.medialibraryDidStartRescan?()
  576. }
  577. }
  578. }