VLCMediaLibraryManager.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*****************************************************************************
  2. * VLCMediaLibraryManager.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. extension Notification.Name {
  13. static let VLCNewFileAddedNotification = Notification.Name("NewFileAddedNotification")
  14. }
  15. // For objc
  16. extension NSNotification {
  17. @objc static let VLCNewFileAddedNotification = Notification.Name.VLCNewFileAddedNotification
  18. }
  19. @objc protocol MediaLibraryObserver: class {
  20. // Video
  21. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  22. didModifyVideo video: [VLCMLMedia])
  23. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  24. didDeleteMediaWithIds ids: [NSNumber])
  25. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  26. didAddVideos videos: [VLCMLMedia])
  27. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  28. didAddShowEpisodes showEpisodes: [VLCMLMedia])
  29. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  30. thumbnailReady media: VLCMLMedia)
  31. // Audio
  32. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  33. didAddAudios audios: [VLCMLMedia])
  34. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  35. didAddArtists artists: [VLCMLArtist])
  36. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  37. didDeleteArtistsWithIds artistsIds: [NSNumber])
  38. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  39. didAddAlbums albums: [VLCMLAlbum])
  40. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  41. didDeleteAlbumsWithIds albumsIds: [NSNumber])
  42. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  43. didAddAlbumTracks albumTracks: [VLCMLMedia])
  44. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  45. didAddGenres genres: [VLCMLGenre])
  46. // Playlist
  47. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  48. didAddPlaylists playlists: [VLCMLPlaylist])
  49. @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
  50. didDeletePlaylistsWithIds playlistsIds: [NSNumber])
  51. }
  52. protocol MediaLibraryMigrationDelegate: class {
  53. func medialibraryDidStartMigration(_ medialibrary: VLCMediaLibraryManager)
  54. func medialibraryDidFinishMigration(_ medialibrary: VLCMediaLibraryManager)
  55. func medialibraryDidStopMigration(_ medialibrary: VLCMediaLibraryManager)
  56. }
  57. class VLCMediaLibraryManager: NSObject {
  58. private static let databaseName: String = "medialibrary.db"
  59. private static let migrationKey: String = "MigratedToVLCMediaLibraryKit"
  60. private var didMigrate = UserDefaults.standard.bool(forKey: VLCMediaLibraryManager.migrationKey)
  61. // Using ObjectIdentifier to avoid duplication and facilitate
  62. // identification of observing object
  63. private var observers = [ObjectIdentifier: Observer]()
  64. private lazy var medialib: VLCMediaLibrary = {
  65. let medialibrary = VLCMediaLibrary()
  66. medialibrary.delegate = self
  67. return medialibrary
  68. }()
  69. weak var migrationDelegate: MediaLibraryMigrationDelegate?
  70. override init() {
  71. super.init()
  72. setupMediaLibrary()
  73. NotificationCenter.default.addObserver(self, selector: #selector(reload),
  74. name: .VLCNewFileAddedNotification, object: nil)
  75. }
  76. // MARK: Private
  77. private func setupMediaDiscovery(at path: String) {
  78. let mediaFileDiscoverer = VLCMediaFileDiscoverer.sharedInstance()
  79. mediaFileDiscoverer?.directoryPath = path
  80. mediaFileDiscoverer?.addObserver(self)
  81. mediaFileDiscoverer?.startDiscovering()
  82. }
  83. private func setupMediaLibrary() {
  84. guard let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first,
  85. let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first else {
  86. preconditionFailure("VLCMediaLibraryManager: Unable to init medialibrary.")
  87. }
  88. setupMediaDiscovery(at: documentPath)
  89. let databasePath = libraryPath + "/MediaLibrary/" + VLCMediaLibraryManager.databaseName
  90. let thumbnailPath = libraryPath + "/MediaLibrary/Thumbnails"
  91. do {
  92. try FileManager.default.createDirectory(atPath: thumbnailPath,
  93. withIntermediateDirectories: true)
  94. } catch let error as NSError {
  95. assertionFailure("Failed to create directory: \(error.localizedDescription)")
  96. }
  97. let medialibraryStatus = medialib.setupMediaLibrary(databasePath: databasePath,
  98. thumbnailPath: thumbnailPath)
  99. switch medialibraryStatus {
  100. case .success:
  101. guard medialib.start() else {
  102. assertionFailure("VLCMediaLibraryManager: Medialibrary failed to start.")
  103. return
  104. }
  105. medialib.reload()
  106. medialib.discover(onEntryPoint: "file://" + documentPath)
  107. case .alreadyInitialized:
  108. assertionFailure("VLCMediaLibraryManager: Medialibrary already initialized.")
  109. case .failed:
  110. preconditionFailure("VLCMediaLibraryManager: Failed to setup medialibrary.")
  111. case .dbReset:
  112. // should still start and discover but warn the user that the db has been wipped
  113. assertionFailure("VLCMediaLibraryManager: The database was resetted, please re-configure.")
  114. }
  115. }
  116. // MARK: Internal
  117. @objc func reload() {
  118. medialib.reload()
  119. }
  120. /// Returns number of *ALL* files(audio and video) present in the medialibrary database
  121. func numberOfFiles() -> Int {
  122. var media = medialib.audioFiles(with: .filename, desc: false)
  123. media += medialib.videoFiles(with: .filename, desc: false)
  124. return media.count
  125. }
  126. /// Returns *ALL* file found for a specified VLCMLMediaType
  127. ///
  128. /// - Parameter type: Type of the media
  129. /// - Returns: Array of VLCMLMedia
  130. func media(ofType type: VLCMLMediaType, sortingCriteria sort: VLCMLSortingCriteria = .filename, desc: Bool = false) -> [VLCMLMedia] {
  131. return type == .video ? medialib.videoFiles(with: sort, desc: desc) : medialib.audioFiles(with: sort, desc: desc)
  132. }
  133. func genre(sortingCriteria sort: VLCMLSortingCriteria = .default, desc: Bool = false) -> [VLCMLGenre] {
  134. return medialib.genres(with: sort, desc: desc)
  135. }
  136. func fetchMedia(with mrl: URL?) -> VLCMLMedia? {
  137. guard let mrl = mrl else {
  138. return nil
  139. }
  140. return medialib.media(withMrl: mrl)
  141. }
  142. @discardableResult
  143. func prepareMigrationIfNeeded() -> Bool {
  144. if !didMigrate {
  145. migrationDelegate?.medialibraryDidStartMigration(self)
  146. return true
  147. }
  148. return false
  149. }
  150. }
  151. // MARK: - Migration
  152. private extension VLCMediaLibraryManager {
  153. func startMigrationIfNeeded() {
  154. guard !didMigrate else {
  155. return
  156. }
  157. guard migrateToNewMediaLibrary() else {
  158. migrationDelegate?.medialibraryDidStopMigration(self)
  159. return
  160. }
  161. migrationDelegate?.medialibraryDidFinishMigration(self)
  162. }
  163. func migrateMedia(_ oldMedialibrary: MLMediaLibrary) -> Bool {
  164. guard let allFiles = MLFile.allFiles() as? [MLFile] else {
  165. assertionFailure("VLCMediaLibraryManager: Migration: Unable to retreive all files")
  166. return false
  167. }
  168. for media in allFiles {
  169. if let newMedia = fetchMedia(with: media.url) {
  170. newMedia.updateTitle(media.title)
  171. newMedia.setPlayCount(media.playCount.uint32Value)
  172. newMedia.setMetadataOf(.progress, intValue: media.lastPosition.int64Value)
  173. newMedia.setMetadataOf(.seen, intValue: media.unread.int64Value)
  174. // Only delete files that are not in playlist
  175. if media.labels.isEmpty {
  176. oldMedialibrary.remove(media)
  177. }
  178. }
  179. }
  180. oldMedialibrary.save()
  181. return true
  182. }
  183. // This private method migrates old playlist and removes file and playlist
  184. // from the old medialibrary.
  185. // Note: This removes **only** files that are in a playlist
  186. func migratePlaylists(_ oldMedialibrary: MLMediaLibrary) -> Bool {
  187. guard let allLabels = MLLabel.allLabels() as? [MLLabel] else {
  188. assertionFailure("VLCMediaLibraryManager: Migration: Unable to retreive all labels")
  189. return false
  190. }
  191. for label in allLabels {
  192. let newPlaylist = createPlaylist(with: label.name)
  193. guard let files = label.files as? Set<MLFile> else {
  194. assertionFailure("VLCMediaLibraryManager: Migration: Unable to retreive 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. return true
  209. }
  210. func migrateToNewMediaLibrary() -> Bool {
  211. guard let oldMedialibrary = MLMediaLibrary.sharedMediaLibrary() as? MLMediaLibrary else {
  212. assertionFailure("VLCMediaLibraryManager: Migration: Unable to retreive old medialibrary")
  213. return false
  214. }
  215. if migrateMedia(oldMedialibrary) && migratePlaylists(oldMedialibrary) {
  216. UserDefaults.standard.set(true, forKey: VLCMediaLibraryManager.migrationKey)
  217. return true
  218. }
  219. return false
  220. }
  221. }
  222. // MARK: - Observer
  223. private extension VLCMediaLibraryManager {
  224. struct Observer {
  225. weak var observer: MediaLibraryObserver?
  226. }
  227. }
  228. extension VLCMediaLibraryManager {
  229. func addObserver(_ observer: MediaLibraryObserver) {
  230. let identifier = ObjectIdentifier(observer)
  231. observers[identifier] = Observer(observer: observer)
  232. }
  233. func removeObserver(_ observer: MediaLibraryObserver) {
  234. let identifier = ObjectIdentifier(observer)
  235. observers.removeValue(forKey: identifier)
  236. }
  237. }
  238. // MARK: MediaLibrary - Audio methods
  239. extension VLCMediaLibraryManager {
  240. func artists(sortingCriteria sort: VLCMLSortingCriteria = .artist, desc: Bool = false) -> [VLCMLArtist] {
  241. return medialib.artists(with: sort, desc: desc, all: true)
  242. }
  243. func albums(sortingCriteria sort: VLCMLSortingCriteria = .album, desc: Bool = false) -> [VLCMLAlbum] {
  244. return medialib.albums(with: sort, desc: desc)
  245. }
  246. }
  247. // MARK: MediaLibrary - Video methods
  248. extension VLCMediaLibraryManager {
  249. func requestThumbnail(for media: [VLCMLMedia]) {
  250. media.forEach() {
  251. guard !$0.isThumbnailGenerated() else { return }
  252. if !medialib.requestThumbnail(for: $0) {
  253. assertionFailure("VLCMediaLibraryManager: Failed to generate thumbnail for: \($0.identifier())")
  254. }
  255. }
  256. }
  257. }
  258. // MARK: MediaLibrary - Playlist methods
  259. extension VLCMediaLibraryManager {
  260. func createPlaylist(with name: String) -> VLCMLPlaylist {
  261. return medialib.createPlaylist(withName: name)
  262. }
  263. func deletePlaylist(with identifier: VLCMLIdentifier) -> Bool {
  264. return medialib.deletePlaylist(withIdentifier: identifier)
  265. }
  266. func getPlaylists(sortingCriteria sort: VLCMLSortingCriteria = .default, desc: Bool = false) -> [VLCMLPlaylist] {
  267. return medialib.playlists(with: sort, desc: desc)
  268. }
  269. }
  270. extension VLCMediaLibraryManager: VLCMediaFileDiscovererDelegate {
  271. func mediaFileAdded(_ filePath: String!, loading isLoading: Bool) {
  272. guard !isLoading else {
  273. return
  274. }
  275. /* exclude media files from backup (QA1719) */
  276. var excludeURL = URL(fileURLWithPath: filePath)
  277. var resourceValue = URLResourceValues()
  278. resourceValue.isExcludedFromBackup = true
  279. do {
  280. try excludeURL.setResourceValues(resourceValue)
  281. } catch let error {
  282. assertionFailure("VLCMediaLibraryManager: VLCMediaFileDiscovererDelegate: \(error.localizedDescription)")
  283. }
  284. reload()
  285. }
  286. func mediaFileDeleted(_ filePath: String!) {
  287. reload()
  288. }
  289. }
  290. // MARK: - VLCMediaLibraryDelegate - Media
  291. extension VLCMediaLibraryManager: VLCMediaLibraryDelegate {
  292. func medialibrary(_ medialibrary: VLCMediaLibrary, didAddMedia media: [VLCMLMedia]) {
  293. let videos = media.filter {( $0.type() == .video )}
  294. let audio = media.filter {( $0.type() == .audio )}
  295. // thumbnails only for videos
  296. requestThumbnail(for: videos)
  297. for observer in observers {
  298. observer.value.observer?.medialibrary?(self, didAddVideos: videos)
  299. observer.value.observer?.medialibrary?(self, didAddAudios: audio)
  300. }
  301. }
  302. func medialibrary(_ medialibrary: VLCMediaLibrary, didModifyMedia media: [VLCMLMedia]) {
  303. let showEpisodes = media.filter {( $0.subtype() == .showEpisode )}
  304. let albumTrack = media.filter {( $0.subtype() == .albumTrack )}
  305. for observer in observers {
  306. observer.value.observer?.medialibrary?(self, didAddShowEpisodes: showEpisodes)
  307. observer.value.observer?.medialibrary?(self, didAddAlbumTracks: albumTrack)
  308. }
  309. }
  310. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteMediaWithIds mediaIds: [NSNumber]) {
  311. for observer in observers {
  312. observer.value.observer?.medialibrary?(self, didDeleteMediaWithIds: mediaIds)
  313. }
  314. }
  315. func medialibrary(_ medialibrary: VLCMediaLibrary, thumbnailReadyFor media: VLCMLMedia, withSuccess success: Bool) {
  316. for observer in observers {
  317. observer.value.observer?.medialibrary?(self, thumbnailReady: media)
  318. }
  319. }
  320. }
  321. // MARK: - VLCMediaLibraryDelegate - Artists
  322. extension VLCMediaLibraryManager {
  323. func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd artists: [VLCMLArtist]) {
  324. for observer in observers {
  325. observer.value.observer?.medialibrary?(self, didAddArtists: artists)
  326. }
  327. }
  328. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteArtistsWithIds artistsIds: [NSNumber]) {
  329. for observer in observers {
  330. observer.value.observer?.medialibrary?(self, didDeleteArtistsWithIds: artistsIds)
  331. }
  332. }
  333. }
  334. // MARK: - VLCMediaLibraryDelegate - Albums
  335. extension VLCMediaLibraryManager {
  336. func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd albums: [VLCMLAlbum]) {
  337. for observer in observers {
  338. observer.value.observer?.medialibrary?(self, didAddAlbums: albums)
  339. }
  340. }
  341. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteAlbumsWithIds albumsIds: [NSNumber]) {
  342. for observer in observers {
  343. observer.value.observer?.medialibrary?(self, didDeleteAlbumsWithIds: albumsIds)
  344. }
  345. }
  346. }
  347. // MARK: - VLCMediaLibraryDelegate - Playlists
  348. extension VLCMediaLibraryManager {
  349. func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd playlists: [VLCMLPlaylist]) {
  350. for observer in observers {
  351. observer.value.observer?.medialibrary?(self, didAddPlaylists: playlists)
  352. }
  353. }
  354. func medialibrary(_ medialibrary: VLCMediaLibrary, didDeletePlaylistsWithIds playlistsIds: [NSNumber]) {
  355. for observer in observers {
  356. observer.value.observer?.medialibrary?(self, didDeletePlaylistsWithIds: playlistsIds)
  357. }
  358. }
  359. }
  360. // MARK: - VLCMediaLibraryDelegate - Discovery
  361. extension VLCMediaLibraryManager {
  362. func medialibrary(_ medialibrary: VLCMediaLibrary, didStartDiscovery entryPoint: String) {
  363. }
  364. func medialibrary(_ medialibrary: VLCMediaLibrary, didCompleteDiscovery entryPoint: String) {
  365. startMigrationIfNeeded()
  366. }
  367. func medialibrary(_ medialibrary: VLCMediaLibrary, didProgressDiscovery entryPoint: String) {
  368. }
  369. func medialibrary(_ medialibrary: VLCMediaLibrary, didUpdateParsingStatsWithPercent percent: UInt32) {
  370. }
  371. }