mirror of
https://github.com/yattee/yattee.git
synced 2026-11-04 10:57:18 +00:00
Open more YouTube link types and playlists from links
Route YouTube search results links to search and nocookie embeds to the video, match YouTube hosts exactly, and report YouTube pages without a video as unsupported instead of sending them to yt-dlp. A watch link inside a playlist shows both the video and the playlist when loaded, and plays the video with the playlist queued around it. URL scheme play, queue and bookmark actions accept playlist links.
This commit is contained in:
@@ -647,7 +647,8 @@ struct OpenLinkFormView: View {
|
||||
if case .externalVideo = destination { return true }
|
||||
return false
|
||||
}
|
||||
return true
|
||||
// YouTube pages that route nowhere are unsupported, not external
|
||||
return !router.isYouTubeHost(url.host)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +658,8 @@ struct OpenLinkFormView: View {
|
||||
let router = URLRouter()
|
||||
return parsedURLs.allSatisfy { url in
|
||||
switch router.route(url) {
|
||||
case .video, .externalVideo, .directMedia, nil: return true
|
||||
case .video, .externalVideo, .directMedia: return true
|
||||
case nil: return !router.isYouTubeHost(url.host)
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
@@ -704,7 +706,8 @@ struct OpenLinkFormView: View {
|
||||
private func loadURLs() async {
|
||||
let urls = parsedURLs
|
||||
guard let appEnvironment, !urls.isEmpty else { return }
|
||||
if urls.count > 1 {
|
||||
// A watch link inside a playlist shows both, like several links
|
||||
if urls.count > 1 || URLRouter().youTubePlaylistContext(urls[0]) != nil {
|
||||
extractedItems = []
|
||||
dismissIfRequested()
|
||||
appEnvironment.navigationCoordinator.navigateToOpenedLink(.openedLinks(urls))
|
||||
@@ -764,7 +767,7 @@ struct OpenLinkFormView: View {
|
||||
|
||||
if !firstVideoPlayed {
|
||||
// Play first video - this expands player
|
||||
playVideo(video, sourceURL: url, appEnvironment: appEnvironment)
|
||||
await playVideo(video, sourceURL: url, appEnvironment: appEnvironment)
|
||||
firstVideoPlayed = true
|
||||
} else {
|
||||
// Add to queue
|
||||
@@ -805,13 +808,16 @@ struct OpenLinkFormView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func playVideo(_ video: Video, sourceURL: URL, appEnvironment: AppEnvironment) {
|
||||
private func playVideo(_ video: Video, sourceURL: URL, appEnvironment: AppEnvironment) async {
|
||||
// Don't pass a specific stream - let the player's selectStreamAndBackend
|
||||
// choose the best video+audio combination. Using streams.first would
|
||||
// incorrectly select audio-only streams for sites like Bilibili.
|
||||
let router = URLRouter()
|
||||
let startTime = router.parseTimestamp(router.unwrapped(sourceURL))
|
||||
appEnvironment.playerService.openVideo(video, startTime: startTime, forceStartTime: startTime != nil)
|
||||
// A watch link inside a playlist queues the playlist around it.
|
||||
// Awaited, so later links queue after the playlist instead of being
|
||||
// cleared by it.
|
||||
await LinkVideoResolver(appEnvironment: appEnvironment).play(video, from: sourceURL, startTime: startTime)
|
||||
}
|
||||
|
||||
// MARK: - Download Action
|
||||
|
||||
@@ -1378,22 +1378,6 @@ struct UnifiedPlaylistDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a unique playlist name by appending (2), (3), etc. if needed.
|
||||
private func generateUniqueName(_ baseName: String, existingTitles: Set<String>) -> String {
|
||||
if !existingTitles.contains(baseName) {
|
||||
return baseName
|
||||
}
|
||||
|
||||
var counter = 2
|
||||
while true {
|
||||
let candidateName = "\(baseName) (\(counter))"
|
||||
if !existingTitles.contains(candidateName) {
|
||||
return candidateName
|
||||
}
|
||||
counter += 1
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports the remote playlist to local storage.
|
||||
private func importToLocal() async {
|
||||
guard let appEnvironment, let remotePlaylist else { return }
|
||||
@@ -1402,8 +1386,7 @@ struct UnifiedPlaylistDetailView: View {
|
||||
importProgress = nil
|
||||
|
||||
// Generate unique name
|
||||
let existingTitles = Set(appEnvironment.dataManager.playlists().map(\.title))
|
||||
let uniqueName = generateUniqueName(remotePlaylist.title, existingTitles: existingTitles)
|
||||
let uniqueName = appEnvironment.dataManager.uniquePlaylistTitle(remotePlaylist.title)
|
||||
|
||||
// Create local playlist
|
||||
let newLocalPlaylist = appEnvironment.dataManager.createPlaylist(
|
||||
|
||||
Reference in New Issue
Block a user