feat: URL deduplication for media library and queue

- On load: merge duplicate URLs (keep first, merge tags/liked/position)
- addItem: check queue for existing URL, show toast if duplicate
- addItemNoPlay: skip if URL already in queue
- Add hasItem(url:) helper
- Playlist references updated when merging duplicates
This commit is contained in:
yumoqing 2026-06-30 23:45:02 +08:00
parent 7c1a84c413
commit 03b8ef8d40
2 changed files with 65 additions and 2 deletions

View File

@ -122,9 +122,62 @@ final class MediaLibrary: ObservableObject {
let store = try? JSONDecoder().decode(LibraryStore.self, from: data) else { return }
items = store.items
playlists = store.playlists
deduplicateItems()
rebuildIndexes()
}
/// URL
private func deduplicateItems() {
var seen: [String: String] = [:] // url first itemID
var firstOccurrence: [String: Int] = [:] // url first index
var toRemove: Set<String> = []
for (i, item) in items.enumerated() {
if let existingID = seen[item.url] {
//
toRemove.insert(item.id)
if let firstIdx = firstOccurrence[item.url] {
// tagslike
if items[firstIdx].lastPosition < item.lastPosition {
items[firstIdx].lastPosition = item.lastPosition
}
if !items[firstIdx].liked && item.liked {
items[firstIdx].liked = true
}
// tags
for tag in item.tags where !items[firstIdx].tags.contains(tag) {
items[firstIdx].tags.append(tag)
}
items[firstIdx].tags.sort()
// IDID
for p in playlists.indices {
if let pos = playlists[p].itemIDs.firstIndex(of: item.id) {
if !playlists[p].itemIDs.contains(existingID) {
playlists[p].itemIDs[pos] = existingID
} else {
playlists[p].itemIDs.remove(at: pos)
}
}
}
}
} else {
seen[item.url] = item.id
firstOccurrence[item.url] = i
}
}
if !toRemove.isEmpty {
items.removeAll { toRemove.contains($0.id) }
NSLog("[MiniPlayer] Deduplicated \(toRemove.count) items")
save()
}
}
/// URL
func hasItem(url: String) -> Bool {
urlIndex[url] != nil
}
/// 线M3UUI
private var saveTask: DispatchWorkItem?

View File

@ -494,8 +494,14 @@ final class PlayerBridge: ObservableObject {
}
func addItem(url: URL, name: String, type: String) {
//
let libItem = library.addItem(url: url.absoluteString, name: name, type: type)
let urlString = url.absoluteString
// URL
if queue.contains(where: { $0.url.absoluteString == urlString }) {
showToast("⚠️ \(name) already in queue")
return
}
//
let libItem = library.addItem(url: urlString, name: name, type: type)
queue.append(MediaItem(id: UUID().uuidString, url: url, name: name,
mediaType: type, libraryID: libItem.id))
if queue.count == 1 { playIndex(0) }
@ -607,6 +613,10 @@ final class PlayerBridge: ObservableObject {
///
func addItemNoPlay(url: URL, name: String, type: String, libraryID: String? = nil) {
//
if queue.contains(where: { $0.url.absoluteString == url.absoluteString }) {
return
}
queue.append(MediaItem(id: UUID().uuidString, url: url, name: name,
mediaType: type, libraryID: libraryID))
//