VLCMediaLibraryManager.swift 13.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*****************************************************************************
 * VLCMediaLibraryManager.swift
 * VLC for iOS
 *****************************************************************************
 * Copyright © 2018 VideoLAN. All rights reserved.
 * Copyright © 2018 Videolabs
 *
 * Authors: Soomin Lee <bubu # mikan.io>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/

13 14 15 16 17 18 19 20 21
extension Notification.Name {
    static let VLCNewFileAddedNotification = Notification.Name("NewFileAddedNotification")
}

// For objc
extension NSNotification {
    @objc static let VLCNewFileAddedNotification = Notification.Name.VLCNewFileAddedNotification
}

22
@objc protocol MediaLibraryObserver: class {
23
    // Video
24
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
25
                                     didModifyVideo video: [VLCMLMedia])
26 27

    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
28
                                     didDeleteMediaWithIds ids: [NSNumber])
29

30
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
31
                                     didAddVideos videos: [VLCMLMedia])
32

33
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
34
                                     didAddShowEpisodes showEpisodes: [VLCMLMedia])
35

36 37 38
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
                                     thumbnailReady media: VLCMLMedia)

39 40
    // Audio
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
41
                                     didAddAudios audios: [VLCMLMedia])
42 43

    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
44
                                     didAddArtists artists: [VLCMLArtist])
Soomin Lee's avatar
Soomin Lee committed
45 46

    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
47
                                     didDeleteArtistsWithIds artistsIds: [NSNumber])
Soomin Lee's avatar
Soomin Lee committed
48

Soomin Lee's avatar
Soomin Lee committed
49
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
50
                                     didAddAlbums albums: [VLCMLAlbum])
Soomin Lee's avatar
Soomin Lee committed
51

52 53 54 55 56 57
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
                                     didDeleteAlbumsWithIds albumsIds: [NSNumber])

    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
                                     didAddAlbumTracks albumTracks: [VLCMLMedia])

Soomin Lee's avatar
Soomin Lee committed
58
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
59
                                     didAddGenres genres: [VLCMLGenre])
60 61 62 63

    // Playlist
    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
                                     didAddPlaylists playlists: [VLCMLPlaylist])
64 65 66

    @objc optional func medialibrary(_ medialibrary: VLCMediaLibraryManager,
                                     didDeletePlaylistsWithIds playlistsIds: [NSNumber])
67 68
}

69 70 71
class VLCMediaLibraryManager: NSObject {
    private static let databaseName: String = "medialibrary.db"

72 73 74 75
    // Using ObjectIdentifier to avoid duplication and facilitate
    // identification of observing object
    private var observers = [ObjectIdentifier: Observer]()

76
    private lazy var medialib: VLCMediaLibrary = {
77 78 79 80 81 82 83 84
        let medialibrary = VLCMediaLibrary()
        medialibrary.delegate = self
        return medialibrary
    }()

    override init() {
        super.init()
        setupMediaLibrary()
85 86
        NotificationCenter.default.addObserver(self, selector: #selector(reload),
                                               name: .VLCNewFileAddedNotification, object: nil)
87 88 89
    }

    // MARK: Private
90

91 92 93 94 95 96 97
    private func setupMediaDiscovery(at path: String) {
        let mediaFileDiscoverer = VLCMediaFileDiscoverer.sharedInstance()
        mediaFileDiscoverer?.directoryPath = path
        mediaFileDiscoverer?.addObserver(self)
        mediaFileDiscoverer?.startDiscovering()
    }

98 99
    private func setupMediaLibrary() {
        guard let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first,
100
            let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first else {
101 102 103
                preconditionFailure("VLCMediaLibraryManager: Unable to init medialibrary.")
        }

104 105
        setupMediaDiscovery(at: documentPath)

106 107 108 109 110 111 112 113 114
        let databasePath = libraryPath + "/MediaLibrary/" + VLCMediaLibraryManager.databaseName
        let thumbnailPath = libraryPath + "/MediaLibrary/Thumbnails"

        do {
            try FileManager.default.createDirectory(atPath: thumbnailPath,
                                                    withIntermediateDirectories: true)
        } catch let error as NSError {
            assertionFailure("Failed to create directory: \(error.localizedDescription)")
        }
115

116 117
        let medialibraryStatus = medialib.setupMediaLibrary(databasePath: databasePath,
                                                            thumbnailPath: thumbnailPath)
118 119 120

        switch medialibraryStatus {
        case .success:
121
            guard medialib.start() else {
122 123 124
                assertionFailure("VLCMediaLibraryManager: Medialibrary failed to start.")
                return
            }
125 126
            medialib.reload()
            medialib.discover(onEntryPoint: "file://" + documentPath)
127 128 129 130 131 132 133 134 135 136 137 138
        case .alreadyInitialized:
            assertionFailure("VLCMediaLibraryManager: Medialibrary already initialized.")
        case .failed:
            preconditionFailure("VLCMediaLibraryManager: Failed to setup medialibrary.")
        case .dbReset:
            // should still start and discover but warn the user that the db has been wipped
            assertionFailure("VLCMediaLibraryManager: The database was resetted, please re-configure.")
        }
    }

    // MARK: Internal

139
    @objc func reload() {
140 141 142
        medialib.reload()
    }

143 144
    /// Returns number of *ALL* files(audio and video) present in the medialibrary database
    func numberOfFiles() -> Int {
145
        var media = medialib.audioFiles(with: .filename, desc: false)
146

147
        media += medialib.videoFiles(with: .filename, desc: false)
148 149 150 151 152 153 154 155
        return media.count
    }


    /// Returns *ALL* file found for a specified VLCMLMediaType
    ///
    /// - Parameter type: Type of the media
    /// - Returns: Array of VLCMLMedia
156 157
    func media(ofType type: VLCMLMediaType, sortingCriteria sort: VLCMLSortingCriteria = .filename, desc: Bool = false) -> [VLCMLMedia] {
        return type == .video ? medialib.videoFiles(with: sort, desc: desc) : medialib.audioFiles(with: sort, desc: desc)
158 159
    }

Soomin Lee's avatar
Soomin Lee committed
160 161 162
    func genre(sortingCriteria sort: VLCMLSortingCriteria = .default, desc: Bool = false) -> [VLCMLGenre] {
        return medialib.genres(with: sort, desc: desc)
    }
163 164
}

165
// MARK: - Observer
166

167 168 169 170 171
private extension VLCMediaLibraryManager {
    struct Observer {
        weak var observer: MediaLibraryObserver?
    }
}
172 173

extension VLCMediaLibraryManager {
174 175
    func addObserver(_ observer: MediaLibraryObserver) {
        let identifier = ObjectIdentifier(observer)
176
        observers[identifier] = Observer(observer: observer)
177 178 179 180 181 182
    }

    func removeObserver(_ observer: MediaLibraryObserver) {
        let identifier = ObjectIdentifier(observer)
        observers.removeValue(forKey: identifier)
    }
183 184
}

185
// MARK: MediaLibrary - Audio methods
186 187

extension VLCMediaLibraryManager {
188 189
    func getArtists(sortingCriteria sort: VLCMLSortingCriteria = .artist, desc: Bool = false) -> [VLCMLArtist] {
        return medialib.artists(with: sort, desc: desc, all: true)
190 191
    }

192 193
    func getAlbums(sortingCriteria sort: VLCMLSortingCriteria = .album, desc: Bool = false) -> [VLCMLAlbum] {
        return medialib.albums(with: sort, desc: desc)
194 195 196
    }
}

197
// MARK: MediaLibrary - Video methods
198 199

extension VLCMediaLibraryManager {
200 201 202 203 204 205 206 207 208 209 210 211
    func requestThumbnail(for media: [VLCMLMedia]) {
        media.forEach() {
                // FIXME: Remind Chouquette! In the medialibrary thumbnails uses absolute paths. Workaround:
                //         - Regenerate path is a thumbnail is detected
                //         - Request a new thumbnail
                // if $0.isThumbnailGenerated() { return }

            if !medialib.requestThumbnail(for: $0) {
                assertionFailure("VLCMediaLibraryManager: Failed to generate thumbnail for: \($0.identifier())")
            }
        }
    }
212 213
}

214 215 216
// MARK: MediaLibrary - Playlist methods

extension VLCMediaLibraryManager {
217 218 219 220 221 222 223 224 225

    func createPlaylist(with name: String) -> VLCMLPlaylist {
        return medialib.createPlaylist(withName: name)
    }

    func deletePlaylist(with identifier: VLCMLIdentifier) -> Bool {
        return medialib.deletePlaylist(withIdentifier: identifier)
    }

226 227 228 229 230
    func getPlaylists(sortingCriteria sort: VLCMLSortingCriteria = .default, desc: Bool = false) -> [VLCMLPlaylist] {
        return medialib.playlists(with: sort, desc: desc)
    }
}

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
extension VLCMediaLibraryManager: VLCMediaFileDiscovererDelegate {
    func mediaFileAdded(_ filePath: String!, loading isLoading: Bool) {
        guard !isLoading else {
            return
        }
        /* exclude media files from backup (QA1719) */
        var excludeURL = URL(fileURLWithPath: filePath)
        var resourceValue = URLResourceValues()

        resourceValue.isExcludedFromBackup = true

        do {
            try excludeURL.setResourceValues(resourceValue)
        } catch let error {
            assertionFailure("VLCMediaLibraryManager: VLCMediaFileDiscovererDelegate: \(error.localizedDescription)")
        }

        reload()
    }

    func mediaFileDeleted(_ filePath: String!) {
        reload()
    }
}

256 257
// MARK: - VLCMediaLibraryDelegate - Media

258 259
extension VLCMediaLibraryManager: VLCMediaLibraryDelegate {
    func medialibrary(_ medialibrary: VLCMediaLibrary, didAddMedia media: [VLCMLMedia]) {
260
        let videos = media.filter {( $0.type() == .video )}
261 262
        let audio = media.filter {( $0.type() == .audio )}

263
        // thumbnails only for videos
264
        requestThumbnail(for: videos)
265

266
        for observer in observers {
267 268
            observer.value.observer?.medialibrary?(self, didAddVideos: videos)
            observer.value.observer?.medialibrary?(self, didAddAudios: audio)
269 270 271
        }
    }

272
    func medialibrary(_ medialibrary: VLCMediaLibrary, didModifyMedia media: [VLCMLMedia]) {
273 274 275 276
        let showEpisodes = media.filter {( $0.subtype() == .showEpisode )}
        let albumTrack = media.filter {( $0.subtype() == .albumTrack )}

        for observer in observers {
277 278
            observer.value.observer?.medialibrary?(self, didAddShowEpisodes: showEpisodes)
            observer.value.observer?.medialibrary?(self, didAddAlbumTracks: albumTrack)
279
        }
280 281
    }

282 283 284 285 286
    func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteMediaWithIds mediaIds: [NSNumber]) {
        for observer in observers {
            observer.value.observer?.medialibrary?(self, didDeleteMediaWithIds: mediaIds)
        }
    }
287 288 289 290 291 292

    func medialibrary(_ medialibrary: VLCMediaLibrary, thumbnailReadyFor media: VLCMLMedia, withSuccess success: Bool) {
        for observer in observers {
            observer.value.observer?.medialibrary?(self, thumbnailReady: media)
        }
    }
293 294 295
}

// MARK: - VLCMediaLibraryDelegate - Artists
296

297
extension VLCMediaLibraryManager {
Soomin Lee's avatar
Soomin Lee committed
298 299
    func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd artists: [VLCMLArtist]) {
        for observer in observers {
300
            observer.value.observer?.medialibrary?(self, didAddArtists: artists)
Soomin Lee's avatar
Soomin Lee committed
301 302
        }
    }
303 304 305 306 307 308

    func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteArtistsWithIds artistsIds: [NSNumber]) {
        for observer in observers {
            observer.value.observer?.medialibrary?(self, didDeleteArtistsWithIds: artistsIds)
        }
    }
309
}
Soomin Lee's avatar
Soomin Lee committed
310

311 312 313
// MARK: - VLCMediaLibraryDelegate - Albums

extension VLCMediaLibraryManager {
Soomin Lee's avatar
Soomin Lee committed
314 315
    func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd albums: [VLCMLAlbum]) {
        for observer in observers {
316
            observer.value.observer?.medialibrary?(self, didAddAlbums: albums)
Soomin Lee's avatar
Soomin Lee committed
317 318
        }
    }
319 320 321 322 323 324

    func medialibrary(_ medialibrary: VLCMediaLibrary, didDeleteAlbumsWithIds albumsIds: [NSNumber]) {
        for observer in observers {
            observer.value.observer?.medialibrary?(self, didDeleteAlbumsWithIds: albumsIds)
        }
    }
325
}
Soomin Lee's avatar
Soomin Lee committed
326

327 328 329 330 331 332 333 334
// MARK: - VLCMediaLibraryDelegate - Playlists

extension VLCMediaLibraryManager {
    func medialibrary(_ medialibrary: VLCMediaLibrary, didAdd playlists: [VLCMLPlaylist]) {
        for observer in observers {
            observer.value.observer?.medialibrary?(self, didAddPlaylists: playlists)
        }
    }
335 336 337 338 339 340

    func medialibrary(_ medialibrary: VLCMediaLibrary, didDeletePlaylistsWithIds playlistsIds: [NSNumber]) {
        for observer in observers {
            observer.value.observer?.medialibrary?(self, didDeletePlaylistsWithIds: playlistsIds)
        }
    }
341 342
}

343 344 345
// MARK: - VLCMediaLibraryDelegate - Discovery

extension VLCMediaLibraryManager {
346 347 348 349 350 351 352 353 354 355 356 357
    func medialibrary(_ medialibrary: VLCMediaLibrary, didStartDiscovery entryPoint: String) {
    }

    func medialibrary(_ medialibrary: VLCMediaLibrary, didCompleteDiscovery entryPoint: String) {
    }

    func medialibrary(_ medialibrary: VLCMediaLibrary, didProgressDiscovery entryPoint: String) {
    }

    func medialibrary(_ medialibrary: VLCMediaLibrary, didUpdateParsingStatsWithPercent percent: UInt32) {
    }
}