MediaLibraryService.swift 22 KB

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