mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Preliminary support for Piped playlist (listing playlists and videos)
This commit is contained in:
parent
836057578f
commit
8d36f57271
@ -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
|
||||||
|
@ -32,6 +32,10 @@ enum VideosApp: String, CaseIterable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var supportsUserPlaylists: Bool {
|
var supportsUserPlaylists: Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPlaylistsEndpointIncludesVideos: Bool {
|
||||||
self == .invidious
|
self == .invidious
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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 ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user