Preliminary support for Piped playlist (listing playlists and videos)

This commit is contained in:
Arkadiusz Fal 2022-04-10 17:07:10 +02:00
parent 836057578f
commit 8d36f57271
6 changed files with 56 additions and 10 deletions

View File

@ -4,7 +4,7 @@ import Siesta
import SwiftyJSON import SwiftyJSON
final class PipedAPI: Service, ObservableObject, VideosAPI { final class PipedAPI: Service, ObservableObject, VideosAPI {
static var authorizedEndpoints = ["subscriptions", "subscribe", "unsubscribe"] static var authorizedEndpoints = ["subscriptions", "subscribe", "unsubscribe", "user/playlists"]
@Published var account: Account! @Published var account: Account!
@ -81,6 +81,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled) return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled)
} }
configureTransformer(pathPattern("user/playlists")) { (content: Entity<JSON>) -> [Playlist] in
content.json.arrayValue.map { self.extractUserPlaylist(from: $0)! }
}
if account.token.isNil { if account.token.isNil {
updateToken() updateToken()
} }
@ -166,7 +170,9 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
var home: Resource? { nil } var home: Resource? { nil }
var popular: Resource? { nil } var popular: Resource? { nil }
var playlists: Resource? { nil } var playlists: Resource? {
resource(baseURL: account.instance.apiURL, path: "user/playlists")
}
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) { func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.instance.apiURL, path: "subscribe") resource(baseURL: account.instance.apiURL, path: "subscribe")
@ -180,7 +186,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
.onCompletion { _ in onCompletion() } .onCompletion { _ in onCompletion() }
} }
func playlist(_: String) -> Resource? { nil } func playlist(_ id: String) -> Resource? {
channelPlaylist(id)
}
func playlistVideo(_: String, _: String) -> Resource? { nil } func playlistVideo(_: String, _: String) -> Resource? { nil }
func playlistVideos(_: String) -> Resource? { nil } func playlistVideos(_: String) -> Resource? { nil }
@ -359,6 +368,14 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
)! )!
} }
private func extractUserPlaylist(from json: JSON) -> Playlist? {
let id = json["id"].stringValue
let title = json["name"].stringValue
let visibility = Playlist.Visibility.private
return Playlist(id: id, title: title, visibility: visibility)
}
private func extractDescription(from content: JSON) -> String? { private func extractDescription(from content: JSON) -> String? {
guard var description = content.dictionaryValue["description"]?.string else { guard var description = content.dictionaryValue["description"]?.string else {
return nil return nil

View File

@ -32,6 +32,10 @@ enum VideosApp: String, CaseIterable {
} }
var supportsUserPlaylists: Bool { var supportsUserPlaylists: Bool {
true
}
var userPlaylistsEndpointIncludesVideos: Bool {
self == .invidious self == .invidious
} }

View File

@ -18,11 +18,11 @@ struct Playlist: Identifiable, Equatable, Hashable {
var title: String var title: String
var visibility: Visibility var visibility: Visibility
var updated: TimeInterval var updated: TimeInterval?
var videos = [Video]() var videos = [Video]()
init(id: String, title: String, visibility: Visibility, updated: TimeInterval, videos: [Video] = []) { init(id: String, title: String, visibility: Visibility, updated: TimeInterval? = nil, videos: [Video] = []) {
self.id = id self.id = id
self.title = title self.title = title
self.visibility = visibility self.visibility = visibility

View File

@ -11,9 +11,7 @@ struct AppSidebarPlaylists: View {
NavigationLink(tag: TabSelection.playlist(playlist.id), selection: $navigation.tabSelection) { NavigationLink(tag: TabSelection.playlist(playlist.id), selection: $navigation.tabSelection) {
LazyView(PlaylistVideosView(playlist)) LazyView(PlaylistVideosView(playlist))
} label: { } label: {
Label(playlist.title, systemImage: RecentsModel.symbolSystemImage(playlist.title)) playlistLabel(playlist)
.backport
.badge(Text("\(playlist.videos.count)"))
} }
.id(playlist.id) .id(playlist.id)
.contextMenu { .contextMenu {
@ -34,6 +32,18 @@ struct AppSidebarPlaylists: View {
} }
} }
@ViewBuilder func playlistLabel(_ playlist: Playlist) -> some View {
let label = Label(playlist.title, systemImage: RecentsModel.symbolSystemImage(playlist.title))
if player.accounts.app.userPlaylistsEndpointIncludesVideos {
label
.backport
.badge(Text("\(playlist.videos.count)"))
} else {
label
}
}
var newPlaylistButton: some View { var newPlaylistButton: some View {
Button(action: { navigation.presentNewPlaylistForm() }) { Button(action: { navigation.presentNewPlaylistForm() }) {
Label("New Playlist", systemImage: "plus.circle") Label("New Playlist", systemImage: "plus.circle")

View File

@ -104,7 +104,8 @@ struct PlaybackBar: View {
} }
guard let video = player.currentVideo, guard let video = player.currentVideo,
let time = player.time else { let time = player.time
else {
return "" return ""
} }

View File

@ -7,8 +7,17 @@ struct PlaylistVideosView: View {
@Environment(\.inNavigationView) private var inNavigationView @Environment(\.inNavigationView) private var inNavigationView
@EnvironmentObject<PlayerModel> private var player @EnvironmentObject<PlayerModel> private var player
@StateObject private var store = Store<ChannelPlaylist>()
var contentItems: [ContentItem] { var contentItems: [ContentItem] {
ContentItem.array(of: playlist.videos) ContentItem.array(of: playlist.videos.isEmpty ? (store.item?.videos ?? []) : playlist.videos)
}
private var resource: Resource? {
let resource = player.accounts.api.playlist(playlist.id)
resource?.addObserver(store)
return resource
} }
var videos: [Video] { var videos: [Video] {
@ -22,6 +31,11 @@ struct PlaylistVideosView: View {
var body: some View { var body: some View {
PlayerControlsView { PlayerControlsView {
VerticalCells(items: contentItems) VerticalCells(items: contentItems)
.onAppear {
if !player.accounts.app.userPlaylistsEndpointIncludesVideos {
resource?.loadIfNeeded()
}
}
#if !os(tvOS) #if !os(tvOS)
.navigationTitle("\(playlist.title) Playlist") .navigationTitle("\(playlist.title) Playlist")
#endif #endif