MediaLibraryService.swift 25 KB

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