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:
parent
7c1a84c413
commit
03b8ef8d40
@ -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] {
|
||||
// 合并:保留更好的名字、tags、like状态、播放位置
|
||||
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()
|
||||
// 合并播放列表引用:将重复ID替换为首条ID
|
||||
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
|
||||
}
|
||||
|
||||
/// 异步保存 — 不阻塞主线程,M3U几千条数据不卡UI
|
||||
private var saveTask: DispatchWorkItem?
|
||||
|
||||
|
||||
@ -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))
|
||||
// 如果队列为空则自动播放第一个
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user