mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Multiplatform playing first steps
This commit is contained in:
parent
24a767e51c
commit
fa07e47a22
@ -1,79 +0,0 @@
|
|||||||
import AVKit
|
|
||||||
import Foundation
|
|
||||||
import Siesta
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct PlayerView: View {
|
|
||||||
@ObservedObject private var store = Store<Video>()
|
|
||||||
|
|
||||||
let resource: Resource
|
|
||||||
|
|
||||||
init(id: String) {
|
|
||||||
resource = InvidiousAPI.shared.video(id)
|
|
||||||
resource.addObserver(store)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
#if os(tvOS)
|
|
||||||
pvc
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
#else
|
|
||||||
if let video = store.item {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text(video.title)
|
|
||||||
|
|
||||||
.bold()
|
|
||||||
|
|
||||||
Text("\(video.author)")
|
|
||||||
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.bold()
|
|
||||||
|
|
||||||
if !video.published.isEmpty || video.views != 0 {
|
|
||||||
HStack(spacing: 8) {
|
|
||||||
#if os(iOS)
|
|
||||||
Text(video.playTime ?? "?")
|
|
||||||
.layoutPriority(1)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if !video.published.isEmpty {
|
|
||||||
Image(systemName: "calendar")
|
|
||||||
Text(video.published)
|
|
||||||
.lineLimit(1)
|
|
||||||
.truncationMode(.middle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if video.views != 0 {
|
|
||||||
Image(systemName: "eye")
|
|
||||||
Text(video.viewsCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.padding(.top)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if os(tvOS)
|
|
||||||
.padding()
|
|
||||||
#else
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
resource.loadIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// swiftlint:disable implicit_return
|
|
||||||
#if !os(macOS)
|
|
||||||
var pvc: PlayerViewController? {
|
|
||||||
guard store.item != nil else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return PlayerViewController(video: store.item!)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// swiftlint:enable implicit_return
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
import AVKit
|
|
||||||
import Foundation
|
|
||||||
import Logging
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct PlayerViewController: UIViewControllerRepresentable {
|
|
||||||
#if os(tvOS)
|
|
||||||
typealias PlayerController = StreamAVPlayerViewController
|
|
||||||
#else
|
|
||||||
typealias PlayerController = AVPlayerViewController
|
|
||||||
#endif
|
|
||||||
|
|
||||||
let logger = Logger(label: "net.arekf.Pearvidious.pvc")
|
|
||||||
|
|
||||||
@ObservedObject private var state: PlayerState
|
|
||||||
|
|
||||||
var video: Video
|
|
||||||
|
|
||||||
init(video: Video) {
|
|
||||||
self.video = video
|
|
||||||
state = PlayerState(video)
|
|
||||||
|
|
||||||
loadStream(video.defaultStreamForProfile(state.profile), loadBest: state.profile.defaultStreamResolution == .hd720pFirstThenBest)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func loadStream(_ stream: Stream?, loadBest: Bool = false) {
|
|
||||||
if stream != state.nextStream {
|
|
||||||
state.loadStream(stream)
|
|
||||||
addTracksAndLoadAssets(stream!, loadBest: loadBest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func addTracksAndLoadAssets(_ stream: Stream, loadBest: Bool = false) {
|
|
||||||
logger.info("adding tracks and loading assets for: \(stream.type), \(stream.description)")
|
|
||||||
|
|
||||||
stream.assets.forEach { asset in
|
|
||||||
asset.loadValuesAsynchronously(forKeys: ["playable"]) {
|
|
||||||
handleAssetLoad(stream, type: asset == stream.videoAsset ? .video : .audio, loadBest: loadBest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func handleAssetLoad(_ stream: Stream, type: AVMediaType, loadBest: Bool = false) {
|
|
||||||
logger.info("handling asset load: \(stream.type), \(stream.description)")
|
|
||||||
|
|
||||||
guard stream != state.currentStream else {
|
|
||||||
logger.warning("IGNORING assets loaded: \(stream.type), \(stream.description)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.loadedAssets.forEach { asset in
|
|
||||||
addTrack(asset, stream: stream, type: type)
|
|
||||||
|
|
||||||
if stream.assetsLoaded {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
logger.info("ALL assets loaded: \(stream.type), \(stream.description)")
|
|
||||||
|
|
||||||
state.playStream(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
if loadBest {
|
|
||||||
loadBestStream()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func addTrack(_ asset: AVURLAsset, stream: Stream, type: AVMediaType? = nil) {
|
|
||||||
let types: [AVMediaType] = stream.type == .adaptive ? [type!] : [.video, .audio]
|
|
||||||
|
|
||||||
types.forEach { state.addTrackToNextComposition(asset, type: $0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func loadBestStream() {
|
|
||||||
guard state.currentStream != video.bestStream else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadStream(video.bestStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUIViewController(context _: Context) -> PlayerController {
|
|
||||||
let controller = PlayerController()
|
|
||||||
|
|
||||||
#if os(tvOS)
|
|
||||||
controller.state = state
|
|
||||||
controller.transportBarCustomMenuItems = [streamingQualityMenu]
|
|
||||||
#endif
|
|
||||||
controller.modalPresentationStyle = .fullScreen
|
|
||||||
controller.player = state.player
|
|
||||||
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIViewController(_ controller: PlayerController, context _: Context) {
|
|
||||||
var items: [UIMenuElement] = []
|
|
||||||
|
|
||||||
if state.nextStream != nil {
|
|
||||||
items.append(actionsMenu)
|
|
||||||
}
|
|
||||||
|
|
||||||
items.append(playbackRateMenu)
|
|
||||||
items.append(streamingQualityMenu)
|
|
||||||
|
|
||||||
#if os(tvOS)
|
|
||||||
controller.transportBarCustomMenuItems = items
|
|
||||||
|
|
||||||
if let skip = skipSegmentAction {
|
|
||||||
if controller.contextualActions.isEmpty {
|
|
||||||
controller.contextualActions = [skip]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
controller.contextualActions = []
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var streamingQualityMenu: UIMenu {
|
|
||||||
UIMenu(title: "Streaming quality", image: UIImage(systemName: "waveform"), children: streamingQualityMenuActions)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var streamingQualityMenuActions: [UIAction] {
|
|
||||||
video.selectableStreams.map { stream in
|
|
||||||
let image = self.state.currentStream == stream ? UIImage(systemName: "checkmark") : nil
|
|
||||||
|
|
||||||
return UIAction(title: stream.description, image: image) { _ in
|
|
||||||
guard state.currentStream != stream else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadStream(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var actionsMenu: UIMenu {
|
|
||||||
UIMenu(title: "Actions", image: UIImage(systemName: "bolt.horizontal.fill"), children: [cancelLoadingAction])
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var cancelLoadingAction: UIAction {
|
|
||||||
UIAction(title: "Cancel loading \(state.nextStream.description) stream") { _ in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
state.nextStream.cancelLoadingAssets()
|
|
||||||
state.cancelLoadingStream(state.nextStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var skipSegmentAction: UIAction? {
|
|
||||||
if state.currentSegment == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return UIAction(title: "Skip \(state.currentSegment!.title())") { _ in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
state.player.seek(to: state.currentSegment!.skipTo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var playbackRateMenu: UIMenu {
|
|
||||||
UIMenu(title: "Playback rate", image: UIImage(systemName: playbackRateMenuImageSystemName), children: playbackRateMenuActions)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var playbackRateMenuImageSystemName: String {
|
|
||||||
if [0.0, 1.0].contains(state.player.rate) {
|
|
||||||
return "speedometer"
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.player.rate < 1.0 ? "tortoise.fill" : "hare.fill"
|
|
||||||
}
|
|
||||||
|
|
||||||
private var playbackRateMenuActions: [UIAction] {
|
|
||||||
PlayerState.availablePlaybackRates.map { rate in
|
|
||||||
let image = state.currentRate == Float(rate) ? UIImage(systemName: "checkmark") : nil
|
|
||||||
|
|
||||||
return UIAction(title: "\(rate)x", image: image) { _ in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
state.setPlayerRate(Float(rate))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,7 +35,9 @@ struct TVNavigationView: View {
|
|||||||
VideoDetailsView(video)
|
VideoDetailsView(video)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $navigationState.showingChannel) {
|
.fullScreenCover(isPresented: $navigationState.showingChannel, onDismiss: {
|
||||||
|
navigationState.showVideoDetailsIfNeeded()
|
||||||
|
}) {
|
||||||
if let channel = navigationState.channel {
|
if let channel = navigationState.channel {
|
||||||
ChannelView(id: channel.id)
|
ChannelView(id: channel.id)
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ import URLImageStore
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct VideoCellView: View {
|
struct VideoCellView: View {
|
||||||
|
@EnvironmentObject<NavigationState> private var navigationState
|
||||||
|
|
||||||
var video: Video
|
var video: Video
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationLink(destination: PlayerView(id: video.id)) {
|
Button(action: { navigationState.playVideo(video) }) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
ZStack(alignment: .trailing) {
|
ZStack(alignment: .trailing) {
|
||||||
if let thumbnail = video.thumbnailURL(quality: .high) {
|
if let thumbnail = video.thumbnailURL(quality: .high) {
|
||||||
|
@ -10,6 +10,8 @@ struct VideoDetailsView: View {
|
|||||||
|
|
||||||
@ObservedObject private var store = Store<Video>()
|
@ObservedObject private var store = Store<Video>()
|
||||||
|
|
||||||
|
@State private var playVideoLinkActive = false
|
||||||
|
|
||||||
var resource: Resource {
|
var resource: Resource {
|
||||||
InvidiousAPI.shared.video(video.id)
|
InvidiousAPI.shared.video(video.id)
|
||||||
}
|
}
|
||||||
@ -22,73 +24,88 @@ struct VideoDetailsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
NavigationView {
|
||||||
Spacer()
|
HStack {
|
||||||
VStack {
|
|
||||||
Spacer()
|
Spacer()
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
|
||||||
if let video = store.item {
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
ZStack(alignment: .bottom) {
|
|
||||||
Group {
|
|
||||||
if let thumbnail = video.thumbnailURL(quality: .maxres) {
|
|
||||||
// to replace with AsyncImage when it is fixed with lazy views
|
|
||||||
URLImage(thumbnail) { image in
|
|
||||||
image
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fill)
|
|
||||||
.frame(width: 1600, height: 800)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: 1600, height: 800)
|
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack {
|
||||||
Text(video.title)
|
Spacer()
|
||||||
.font(.system(size: 40))
|
|
||||||
|
|
||||||
HStack {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
NavigationLink(destination: PlayerView(id: video.id)) {
|
if let video = store.item {
|
||||||
HStack(spacing: 8) {
|
VStack(alignment: .center) {
|
||||||
Image(systemName: "play.rectangle.fill")
|
ZStack(alignment: .bottom) {
|
||||||
|
Group {
|
||||||
Text("Play")
|
if let thumbnail = video.thumbnailURL(quality: .maxres) {
|
||||||
|
// to replace with AsyncImage when it is fixed with lazy views
|
||||||
|
URLImage(thumbnail) { image in
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 1600, height: 800)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openChannelButton
|
|
||||||
}
|
}
|
||||||
|
.frame(width: 1600, height: 800)
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(video.title)
|
||||||
|
.font(.system(size: 40))
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
playVideoButton
|
||||||
|
|
||||||
|
openChannelButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(40)
|
||||||
|
.frame(width: 1600, alignment: .leading)
|
||||||
|
.background(.thinMaterial)
|
||||||
}
|
}
|
||||||
.padding(40)
|
.mask(RoundedRectangle(cornerRadius: 20))
|
||||||
.frame(width: 1600, alignment: .leading)
|
VStack {
|
||||||
.background(.thinMaterial)
|
Text(video.description)
|
||||||
|
.lineLimit(nil)
|
||||||
|
.focusable()
|
||||||
|
}.frame(width: 1600, alignment: .leading)
|
||||||
}
|
}
|
||||||
.mask(RoundedRectangle(cornerRadius: 20))
|
|
||||||
VStack {
|
|
||||||
Text(video.description)
|
|
||||||
.lineLimit(nil)
|
|
||||||
.focusable()
|
|
||||||
}.frame(width: 1600, alignment: .leading)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
.background(.thinMaterial)
|
.background(.thinMaterial)
|
||||||
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
resource.loadIfNeeded()
|
resource.loadIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var playVideoButton: some View {
|
||||||
|
Button(action: {
|
||||||
|
navigationState.returnToDetails = true
|
||||||
|
playVideoLinkActive = true
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(systemName: "play.rectangle.fill")
|
||||||
|
Text("Play")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(NavigationLink(destination: VideoPlayerView(video), isActive: $playVideoLinkActive) { EmptyView() }.hidden())
|
||||||
|
}
|
||||||
|
|
||||||
var openChannelButton: some View {
|
var openChannelButton: some View {
|
||||||
let channel = Channel.from(video: store.item!)
|
let channel = Channel.from(video: store.item!)
|
||||||
|
|
||||||
return Button("Open \(channel.name) channel") {
|
return Button("Open \(channel.name) channel") {
|
||||||
navigationState.openChannel(channel)
|
navigationState.openChannel(channel)
|
||||||
|
navigationState.returnToDetails = true
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ import URLImage
|
|||||||
import URLImageStore
|
import URLImageStore
|
||||||
|
|
||||||
struct VideoListRowView: View {
|
struct VideoListRowView: View {
|
||||||
|
@EnvironmentObject<NavigationState> private var navigationState
|
||||||
|
|
||||||
@Environment(\.isFocused) private var focused: Bool
|
@Environment(\.isFocused) private var focused: Bool
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -12,23 +14,27 @@ struct VideoListRowView: View {
|
|||||||
var video: Video
|
var video: Video
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(tvOS) || os(macOS)
|
#if os(tvOS)
|
||||||
NavigationLink(destination: PlayerView(id: video.id)) {
|
Button(action: { navigationState.playVideo(video) }) {
|
||||||
#if os(tvOS)
|
horizontalRow(detailsOnThumbnail: false)
|
||||||
horizontalRow(detailsOnThumbnail: false)
|
}
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
verticalRow
|
NavigationLink(destination: VideoPlayerView(video)) {
|
||||||
#endif
|
verticalRow
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
ZStack {
|
ZStack {
|
||||||
if verticalSizeClass == .compact {
|
#if os(macOS)
|
||||||
horizontalRow(padding: 4)
|
|
||||||
} else {
|
|
||||||
verticalRow
|
verticalRow
|
||||||
}
|
#else
|
||||||
|
if verticalSizeClass == .compact {
|
||||||
|
horizontalRow(padding: 4)
|
||||||
|
} else {
|
||||||
|
verticalRow
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
NavigationLink(destination: PlayerView(id: video.id)) {
|
NavigationLink(destination: VideoPlayerView(video)) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
19
Apple TV/VideoLoading.swift
Normal file
19
Apple TV/VideoLoading.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct VideoLoading: View {
|
||||||
|
var video: Video
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
Text(video.title)
|
||||||
|
|
||||||
|
Text("Loading...")
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,13 @@ import Defaults
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct VideosView: View {
|
struct VideosView: View {
|
||||||
|
@EnvironmentObject<NavigationState> private var navigationState
|
||||||
|
|
||||||
@State private var profile = Profile()
|
@State private var profile = Profile()
|
||||||
|
|
||||||
@Default(.layout) var layout
|
#if os(tvOS)
|
||||||
|
@Default(.layout) var layout
|
||||||
|
#endif
|
||||||
|
|
||||||
@Default(.showingAddToPlaylist) var showingAddToPlaylist
|
@Default(.showingAddToPlaylist) var showingAddToPlaylist
|
||||||
|
|
||||||
@ -31,9 +35,15 @@ struct VideosView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
|
.fullScreenCover(isPresented: $navigationState.showingVideo) {
|
||||||
|
if let video = navigationState.video {
|
||||||
|
VideoPlayerView(video)
|
||||||
|
}
|
||||||
|
}
|
||||||
.fullScreenCover(isPresented: $showingAddToPlaylist) {
|
.fullScreenCover(isPresented: $showingAddToPlaylist) {
|
||||||
AddToPlaylistView()
|
AddToPlaylistView()
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
Mac/Player.swift
Normal file
14
Mac/Player.swift
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct Player: NSViewControllerRepresentable {
|
||||||
|
var video: Video!
|
||||||
|
|
||||||
|
func makeNSViewController(context _: Context) -> PlayerViewController {
|
||||||
|
let controller = PlayerViewController()
|
||||||
|
controller.video = video
|
||||||
|
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSViewController(_: PlayerViewController, context _: Context) {}
|
||||||
|
}
|
36
Mac/PlayerViewController.swift
Normal file
36
Mac/PlayerViewController.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import AVKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
final class PlayerViewController: NSViewController {
|
||||||
|
var video: Video!
|
||||||
|
|
||||||
|
var player = AVPlayer()
|
||||||
|
var playerState: PlayerState! = PlayerState()
|
||||||
|
var playerView = AVPlayerView()
|
||||||
|
|
||||||
|
override func viewDidDisappear() {
|
||||||
|
playerView.player?.replaceCurrentItem(with: nil)
|
||||||
|
playerView.player = nil
|
||||||
|
|
||||||
|
playerState.player = nil
|
||||||
|
playerState = nil
|
||||||
|
|
||||||
|
super.viewDidDisappear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
guard playerState.player == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
playerState.player = player
|
||||||
|
playerView.player = playerState.player
|
||||||
|
|
||||||
|
playerView.controlsStyle = .floating
|
||||||
|
playerView.showsFullScreenToggleButton = true
|
||||||
|
|
||||||
|
view = playerView
|
||||||
|
|
||||||
|
playerState.loadVideo(video)
|
||||||
|
}
|
||||||
|
}
|
@ -8,9 +8,13 @@ final class NavigationState: ObservableObject {
|
|||||||
@Published var channel: Channel?
|
@Published var channel: Channel?
|
||||||
|
|
||||||
@Published var showingVideoDetails = false
|
@Published var showingVideoDetails = false
|
||||||
|
@Published var showingVideo = false
|
||||||
@Published var video: Video?
|
@Published var video: Video?
|
||||||
|
|
||||||
|
@Published var returnToDetails = false
|
||||||
|
|
||||||
func openChannel(_ channel: Channel) {
|
func openChannel(_ channel: Channel) {
|
||||||
|
returnToDetails = false
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
showingChannel = true
|
showingChannel = true
|
||||||
}
|
}
|
||||||
@ -30,6 +34,16 @@ final class NavigationState: ObservableObject {
|
|||||||
video = nil
|
video = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func playVideo(_ video: Video) {
|
||||||
|
self.video = video
|
||||||
|
showingVideo = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func showVideoDetailsIfNeeded() {
|
||||||
|
showingVideoDetails = returnToDetails
|
||||||
|
returnToDetails = false
|
||||||
|
}
|
||||||
|
|
||||||
var tabSelectionOptionalBinding: Binding<TabSelection?> {
|
var tabSelectionOptionalBinding: Binding<TabSelection?> {
|
||||||
Binding<TabSelection?>(
|
Binding<TabSelection?>(
|
||||||
get: {
|
get: {
|
||||||
|
@ -8,27 +8,27 @@ import Logging
|
|||||||
final class PlayerState: ObservableObject {
|
final class PlayerState: ObservableObject {
|
||||||
let logger = Logger(label: "net.arekf.Pearvidious.ps")
|
let logger = Logger(label: "net.arekf.Pearvidious.ps")
|
||||||
|
|
||||||
var video: Video
|
var video: Video!
|
||||||
|
private(set) var composition = AVMutableComposition()
|
||||||
|
private(set) var nextComposition = AVMutableComposition()
|
||||||
|
|
||||||
@Published private(set) var player: AVPlayer! = AVPlayer()
|
private(set) var currentStream: Stream!
|
||||||
@Published private(set) var composition = AVMutableComposition()
|
|
||||||
@Published private(set) var nextComposition = AVMutableComposition()
|
|
||||||
|
|
||||||
@Published private(set) var currentStream: Stream!
|
private(set) var nextStream: Stream!
|
||||||
|
private(set) var streamLoading = false
|
||||||
|
|
||||||
@Published private(set) var nextStream: Stream!
|
private(set) var currentTime: CMTime?
|
||||||
@Published private(set) var streamLoading = false
|
private(set) var savedTime: CMTime?
|
||||||
|
|
||||||
@Published private(set) var currentTime: CMTime?
|
var currentSegment: Segment?
|
||||||
@Published private(set) var savedTime: CMTime?
|
|
||||||
|
|
||||||
@Published var currentSegment: Segment?
|
|
||||||
|
|
||||||
private(set) var profile = Profile()
|
private(set) var profile = Profile()
|
||||||
|
|
||||||
@Published private(set) var currentRate: Float = 0.0
|
private(set) var currentRate: Float = 0.0
|
||||||
static let availablePlaybackRates: [Double] = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
|
static let availablePlaybackRates: [Double] = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
|
||||||
|
|
||||||
|
var player: AVPlayer!
|
||||||
|
|
||||||
var playerItem: AVPlayerItem {
|
var playerItem: AVPlayerItem {
|
||||||
let playerItem = AVPlayerItem(asset: composition)
|
let playerItem = AVPlayerItem(asset: composition)
|
||||||
|
|
||||||
@ -39,7 +39,6 @@ final class PlayerState: ObservableObject {
|
|||||||
]
|
]
|
||||||
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
|
|
||||||
if let thumbnailData = try? Data(contentsOf: video.thumbnailURL(quality: .high)!),
|
if let thumbnailData = try? Data(contentsOf: video.thumbnailURL(quality: .high)!),
|
||||||
let image = UIImage(data: thumbnailData),
|
let image = UIImage(data: thumbnailData),
|
||||||
let pngData = image.pngData()
|
let pngData = image.pngData()
|
||||||
@ -49,7 +48,6 @@ final class PlayerState: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playerItem.externalMetadata = externalMetadata
|
playerItem.externalMetadata = externalMetadata
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
playerItem.preferredForwardBufferDuration = 10
|
playerItem.preferredForwardBufferDuration = 10
|
||||||
@ -57,33 +55,100 @@ final class PlayerState: ObservableObject {
|
|||||||
return playerItem
|
return playerItem
|
||||||
}
|
}
|
||||||
|
|
||||||
var segmentsProvider: SponsorBlockAPI
|
var segmentsProvider: SponsorBlockAPI?
|
||||||
var timeObserver: Any?
|
var timeObserver: Any?
|
||||||
|
|
||||||
init(_ video: Video) {
|
init(_ video: Video? = nil) {
|
||||||
self.video = video
|
self.video = video
|
||||||
|
|
||||||
segmentsProvider = SponsorBlockAPI(video.id)
|
if self.video != nil {
|
||||||
segmentsProvider.load()
|
segmentsProvider = SponsorBlockAPI(self.video.id)
|
||||||
|
segmentsProvider!.load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
destroyPlayer()
|
destroyPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadStream(_ stream: Stream?) {
|
func loadVideo(_ video: Video?) {
|
||||||
guard nextStream != stream else {
|
guard video != nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InvidiousAPI.shared.video(video!.id).load().onSuccess { response in
|
||||||
|
if let video: Video = response.typedContent() {
|
||||||
|
self.video = video
|
||||||
|
Task {
|
||||||
|
let loadBest = self.profile.defaultStreamResolution == .hd720pFirstThenBest
|
||||||
|
await self.loadStream(video.defaultStreamForProfile(self.profile)!, loadBest: loadBest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadStream(_ stream: Stream, loadBest: Bool = false) async {
|
||||||
nextStream?.cancelLoadingAssets()
|
nextStream?.cancelLoadingAssets()
|
||||||
removeTracksFromNextComposition()
|
// removeTracksFromNextComposition()
|
||||||
|
|
||||||
|
nextComposition = AVMutableComposition()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.streamLoading = true
|
self.streamLoading = true
|
||||||
self.nextStream = stream
|
self.nextStream = stream
|
||||||
}
|
}
|
||||||
logger.info("replace streamToLoad: \(nextStream?.description ?? "nil"), streamLoading \(streamLoading)")
|
logger.info("replace streamToLoad: \(nextStream?.description ?? "nil"), streamLoading \(streamLoading)")
|
||||||
|
|
||||||
|
await addTracksAndLoadAssets(stream, loadBest: loadBest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func addTracksAndLoadAssets(_ stream: Stream, loadBest: Bool = false) async {
|
||||||
|
logger.info("adding tracks and loading assets for: \(stream.type), \(stream.description)")
|
||||||
|
|
||||||
|
stream.assets.forEach { asset in
|
||||||
|
Task.init {
|
||||||
|
if try await asset.load(.isPlayable) {
|
||||||
|
handleAssetLoad(stream, asset: asset, type: asset == stream.videoAsset ? .video : .audio, loadBest: loadBest)
|
||||||
|
|
||||||
|
if stream.assetsLoaded {
|
||||||
|
logger.info("ALL assets loaded: \(stream.type), \(stream.description)")
|
||||||
|
|
||||||
|
playStream(stream)
|
||||||
|
|
||||||
|
if loadBest {
|
||||||
|
await self.loadBestStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func handleAssetLoad(_ stream: Stream, asset: AVURLAsset, type: AVMediaType, loadBest _: Bool = false) {
|
||||||
|
logger.info("handling asset load: \(stream.type), \(type) \(stream.description)")
|
||||||
|
|
||||||
|
guard stream != currentStream else {
|
||||||
|
logger.warning("IGNORING assets loaded: \(stream.type), \(stream.description)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack(asset, stream: stream, type: type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func addTrack(_ asset: AVURLAsset, stream: Stream, type: AVMediaType? = nil) {
|
||||||
|
let types: [AVMediaType] = stream.type == .adaptive ? [type!] : [.video, .audio]
|
||||||
|
|
||||||
|
types.forEach { addTrackToNextComposition(asset, type: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func loadBestStream() async {
|
||||||
|
guard currentStream != video.bestStream else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let bestStream = video.bestStream {
|
||||||
|
await loadStream(bestStream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamDidLoad(_ stream: Stream?) {
|
func streamDidLoad(_ stream: Stream?) {
|
||||||
@ -97,7 +162,7 @@ final class PlayerState: ObservableObject {
|
|||||||
nextStream = nil
|
nextStream = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
addTimeObserver()
|
// addTimeObserver()
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelLoadingStream(_ stream: Stream) {
|
func cancelLoadingStream(_ stream: Stream) {
|
||||||
@ -112,21 +177,22 @@ final class PlayerState: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func playStream(_ stream: Stream) {
|
func playStream(_ stream: Stream) {
|
||||||
guard player != nil else {
|
// guard player != nil else {
|
||||||
return
|
// fatalError("player does not exists for playing")
|
||||||
}
|
// }
|
||||||
|
|
||||||
logger.warning("loading \(stream.description) to player")
|
logger.warning("loading \(stream.description) to player")
|
||||||
|
|
||||||
saveTime()
|
saveTime()
|
||||||
replaceCompositionTracks()
|
replaceCompositionTracks()
|
||||||
|
|
||||||
player.replaceCurrentItem(with: playerItem)
|
player!.replaceCurrentItem(with: playerItem)
|
||||||
streamDidLoad(stream)
|
streamDidLoad(stream)
|
||||||
|
|
||||||
player.play()
|
DispatchQueue.main.async {
|
||||||
|
self.player?.play()
|
||||||
seekToSavedTime()
|
self.seekToSavedTime()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTrackToNextComposition(_ asset: AVURLAsset, type: AVMediaType) {
|
func addTrackToNextComposition(_ asset: AVURLAsset, type: AVMediaType) {
|
||||||
@ -198,27 +264,34 @@ final class PlayerState: ObservableObject {
|
|||||||
func destroyPlayer() {
|
func destroyPlayer() {
|
||||||
logger.critical("destroying player")
|
logger.critical("destroying player")
|
||||||
|
|
||||||
player.currentItem?.tracks.forEach { $0.assetTrack?.asset?.cancelLoading() }
|
player?.currentItem?.tracks.forEach { $0.assetTrack?.asset?.cancelLoading() }
|
||||||
|
|
||||||
currentStream?.cancelLoadingAssets()
|
currentStream?.cancelLoadingAssets()
|
||||||
nextStream?.cancelLoadingAssets()
|
nextStream?.cancelLoadingAssets()
|
||||||
|
|
||||||
player.cancelPendingPrerolls()
|
player?.cancelPendingPrerolls()
|
||||||
player.replaceCurrentItem(with: nil)
|
player?.replaceCurrentItem(with: nil)
|
||||||
|
|
||||||
if timeObserver != nil {
|
if timeObserver != nil {
|
||||||
player.removeTimeObserver(timeObserver!)
|
player?.removeTimeObserver(timeObserver!)
|
||||||
|
timeObserver = nil
|
||||||
}
|
}
|
||||||
|
player = nil
|
||||||
|
currentStream = nil
|
||||||
|
nextStream = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTimeObserver() {
|
func addTimeObserver() {
|
||||||
let interval = CMTime(value: 1, timescale: 1)
|
let interval = CMTime(value: 1, timescale: 1)
|
||||||
timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
|
timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
|
||||||
|
guard self.player != nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
self.currentTime = time
|
self.currentTime = time
|
||||||
|
|
||||||
let currentSegment = self.segmentsProvider.segments.first { $0.timeInSegment(time) }
|
self.currentSegment = self.segmentsProvider?.segments.first { $0.timeInSegment(time) }
|
||||||
|
|
||||||
if let segment = currentSegment {
|
if let segment = self.currentSegment {
|
||||||
if self.profile.skippedSegmentsCategories.contains(segment.category) {
|
if self.profile.skippedSegmentsCategories.contains(segment.category) {
|
||||||
if segment.shouldSkip(self.currentTime!) {
|
if segment.shouldSkip(self.currentTime!) {
|
||||||
self.player.seek(to: segment.skipTo)
|
self.player.seek(to: segment.skipTo)
|
||||||
@ -229,8 +302,6 @@ final class PlayerState: ObservableObject {
|
|||||||
if self.player.rate != self.currentRate, self.player.rate != 0, self.currentRate != 0 {
|
if self.player.rate != self.currentRate, self.player.rate != 0, self.currentRate != 0 {
|
||||||
self.player.rate = self.currentRate
|
self.player.rate = self.currentRate
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentSegment = currentSegment
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import Defaults
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Profile {
|
struct Profile {
|
||||||
var defaultStreamResolution: DefaultStreamResolution = .hd720p
|
var defaultStreamResolution: DefaultStreamResolution = .hd720pFirstThenBest
|
||||||
|
|
||||||
var skippedSegmentsCategories = [String]() // SponsorBlockSegmentsProvider.categories
|
var skippedSegmentsCategories = [String]() // SponsorBlockSegmentsProvider.categories
|
||||||
|
|
||||||
|
@ -27,9 +27,8 @@
|
|||||||
372915E62687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
372915E62687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
||||||
372915E72687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
372915E72687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
||||||
372915E82687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
372915E82687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
||||||
372915EA2687EBA500F5A35B /* ListingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E92687EBA500F5A35B /* ListingLayout.swift */; };
|
|
||||||
372915EB2687EBA500F5A35B /* ListingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E92687EBA500F5A35B /* ListingLayout.swift */; };
|
|
||||||
372915EC2687EBA500F5A35B /* ListingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E92687EBA500F5A35B /* ListingLayout.swift */; };
|
372915EC2687EBA500F5A35B /* ListingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E92687EBA500F5A35B /* ListingLayout.swift */; };
|
||||||
|
372F954A26A4D27000502766 /* VideoLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372F954926A4D0C900502766 /* VideoLoading.swift */; };
|
||||||
373CFABE26966148003CB2C6 /* CoverSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFABD26966115003CB2C6 /* CoverSectionView.swift */; };
|
373CFABE26966148003CB2C6 /* CoverSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFABD26966115003CB2C6 /* CoverSectionView.swift */; };
|
||||||
373CFABF26966149003CB2C6 /* CoverSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFABD26966115003CB2C6 /* CoverSectionView.swift */; };
|
373CFABF26966149003CB2C6 /* CoverSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFABD26966115003CB2C6 /* CoverSectionView.swift */; };
|
||||||
373CFAC026966149003CB2C6 /* CoverSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFABD26966115003CB2C6 /* CoverSectionView.swift */; };
|
373CFAC026966149003CB2C6 /* CoverSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFABD26966115003CB2C6 /* CoverSectionView.swift */; };
|
||||||
@ -63,7 +62,6 @@
|
|||||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||||
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||||
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||||
3741B5302676213400125C5E /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3741B52F2676213400125C5E /* PlayerViewController.swift */; };
|
|
||||||
3755A0C2269B772000F67988 /* StreamAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3755A0C1269B772000F67988 /* StreamAVPlayerViewController.swift */; };
|
3755A0C2269B772000F67988 /* StreamAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3755A0C1269B772000F67988 /* StreamAVPlayerViewController.swift */; };
|
||||||
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; };
|
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; };
|
||||||
376578862685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; };
|
376578862685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; };
|
||||||
@ -74,7 +72,6 @@
|
|||||||
376578912685490700D4EA09 /* PlaylistsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578902685490700D4EA09 /* PlaylistsView.swift */; };
|
376578912685490700D4EA09 /* PlaylistsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578902685490700D4EA09 /* PlaylistsView.swift */; };
|
||||||
376578922685490700D4EA09 /* PlaylistsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578902685490700D4EA09 /* PlaylistsView.swift */; };
|
376578922685490700D4EA09 /* PlaylistsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578902685490700D4EA09 /* PlaylistsView.swift */; };
|
||||||
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578902685490700D4EA09 /* PlaylistsView.swift */; };
|
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578902685490700D4EA09 /* PlaylistsView.swift */; };
|
||||||
3774063826999F7C00304C93 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1822671681B00C925CA /* PlayerView.swift */; };
|
|
||||||
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
||||||
377A20AA2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
377A20AA2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
||||||
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
||||||
@ -92,8 +89,6 @@
|
|||||||
377FC7E3267A084A00A6BBAF /* VideoListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoListRowView.swift */; };
|
377FC7E3267A084A00A6BBAF /* VideoListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoListRowView.swift */; };
|
||||||
377FC7E4267A084E00A6BBAF /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
377FC7E4267A084E00A6BBAF /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
||||||
377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
||||||
377FC7E6267A085600A6BBAF /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1822671681B00C925CA /* PlayerView.swift */; };
|
|
||||||
377FC7E9267A085D00A6BBAF /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3741B52F2676213400125C5E /* PlayerViewController.swift */; };
|
|
||||||
377FC7ED267A0A0800A6BBAF /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7EC267A0A0800A6BBAF /* SwiftyJSON */; };
|
377FC7ED267A0A0800A6BBAF /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7EC267A0A0800A6BBAF /* SwiftyJSON */; };
|
||||||
377FC7EF267A0A0800A6BBAF /* URLImage in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7EE267A0A0800A6BBAF /* URLImage */; };
|
377FC7EF267A0A0800A6BBAF /* URLImage in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7EE267A0A0800A6BBAF /* URLImage */; };
|
||||||
377FC7F1267A0A0800A6BBAF /* URLImageStore in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7F0267A0A0800A6BBAF /* URLImageStore */; };
|
377FC7F1267A0A0800A6BBAF /* URLImageStore in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7F0267A0A0800A6BBAF /* URLImageStore */; };
|
||||||
@ -129,9 +124,6 @@
|
|||||||
37B767DC2677C3CA0098BAA8 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerState.swift */; };
|
37B767DC2677C3CA0098BAA8 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerState.swift */; };
|
||||||
37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerState.swift */; };
|
37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerState.swift */; };
|
||||||
37B767E02678C5BF0098BAA8 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 37B767DF2678C5BF0098BAA8 /* Logging */; };
|
37B767E02678C5BF0098BAA8 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 37B767DF2678C5BF0098BAA8 /* Logging */; };
|
||||||
37B76E96268747C900CE5671 /* OptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B76E95268747C900CE5671 /* OptionsView.swift */; };
|
|
||||||
37B76E97268747C900CE5671 /* OptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B76E95268747C900CE5671 /* OptionsView.swift */; };
|
|
||||||
37B76E98268747C900CE5671 /* OptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B76E95268747C900CE5671 /* OptionsView.swift */; };
|
|
||||||
37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */; };
|
37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */; };
|
||||||
37BADCA52699FB72009BE4FB /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 37BADCA42699FB72009BE4FB /* Alamofire */; };
|
37BADCA52699FB72009BE4FB /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 37BADCA42699FB72009BE4FB /* Alamofire */; };
|
||||||
37BADCA7269A552E009BE4FB /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 37BADCA6269A552E009BE4FB /* Alamofire */; };
|
37BADCA7269A552E009BE4FB /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 37BADCA6269A552E009BE4FB /* Alamofire */; };
|
||||||
@ -149,6 +141,16 @@
|
|||||||
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; };
|
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; };
|
||||||
37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07B42698AA4D003EBB87 /* ContentView.swift */; };
|
37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07B42698AA4D003EBB87 /* ContentView.swift */; };
|
||||||
37BD07CA2698FBE5003EBB87 /* AppSidebarNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */; };
|
37BD07CA2698FBE5003EBB87 /* AppSidebarNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */; };
|
||||||
|
37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
||||||
|
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
||||||
|
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
||||||
|
37BE0BD326A1D4780092E2DB /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* Player.swift */; };
|
||||||
|
37BE0BD426A1D47D0092E2DB /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* Player.swift */; };
|
||||||
|
37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */; };
|
||||||
|
37BE0BD726A1D4A90092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */; };
|
||||||
|
37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD926A214630092E2DB /* PlayerViewController.swift */; };
|
||||||
|
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BDB26A2367F0092E2DB /* Player.swift */; };
|
||||||
|
37BE0BE526A336910092E2DB /* OptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B76E95268747C900CE5671 /* OptionsView.swift */; };
|
||||||
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
||||||
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
||||||
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
||||||
@ -178,7 +180,6 @@
|
|||||||
37D4B176267164B000C925CA /* PearvidiousUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B175267164B000C925CA /* PearvidiousUITests.swift */; };
|
37D4B176267164B000C925CA /* PearvidiousUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B175267164B000C925CA /* PearvidiousUITests.swift */; };
|
||||||
37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* PearvidiousApp.swift */; };
|
37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* PearvidiousApp.swift */; };
|
||||||
37D4B1812671653A00C925CA /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; };
|
37D4B1812671653A00C925CA /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; };
|
||||||
37D4B1842671684E00C925CA /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1822671681B00C925CA /* PlayerView.swift */; };
|
|
||||||
37D4B1862671691600C925CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37D4B0C42671614800C925CA /* Assets.xcassets */; };
|
37D4B1862671691600C925CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37D4B0C42671614800C925CA /* Assets.xcassets */; };
|
||||||
37D4B18E26717B3800C925CA /* VideoListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoListRowView.swift */; };
|
37D4B18E26717B3800C925CA /* VideoListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoListRowView.swift */; };
|
||||||
37D4B19726717E1500C925CA /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19626717E1500C925CA /* Video.swift */; };
|
37D4B19726717E1500C925CA /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19626717E1500C925CA /* Video.swift */; };
|
||||||
@ -237,6 +238,7 @@
|
|||||||
371F2F19269B43D300E4A7AB /* NavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationState.swift; sourceTree = "<group>"; };
|
371F2F19269B43D300E4A7AB /* NavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationState.swift; sourceTree = "<group>"; };
|
||||||
372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
|
372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
|
||||||
372915E92687EBA500F5A35B /* ListingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListingLayout.swift; sourceTree = "<group>"; };
|
372915E92687EBA500F5A35B /* ListingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListingLayout.swift; sourceTree = "<group>"; };
|
||||||
|
372F954926A4D0C900502766 /* VideoLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoLoading.swift; sourceTree = "<group>"; };
|
||||||
373CFABD26966115003CB2C6 /* CoverSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverSectionView.swift; sourceTree = "<group>"; };
|
373CFABD26966115003CB2C6 /* CoverSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverSectionView.swift; sourceTree = "<group>"; };
|
||||||
373CFAC126966159003CB2C6 /* CoverSectionRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverSectionRowView.swift; sourceTree = "<group>"; };
|
373CFAC126966159003CB2C6 /* CoverSectionRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverSectionRowView.swift; sourceTree = "<group>"; };
|
||||||
373CFAC52696617C003CB2C6 /* SearchOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOptionsView.swift; sourceTree = "<group>"; };
|
373CFAC52696617C003CB2C6 /* SearchOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOptionsView.swift; sourceTree = "<group>"; };
|
||||||
@ -248,7 +250,6 @@
|
|||||||
373CFAE226974812003CB2C6 /* PlaylistVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistVisibility.swift; sourceTree = "<group>"; };
|
373CFAE226974812003CB2C6 /* PlaylistVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistVisibility.swift; sourceTree = "<group>"; };
|
||||||
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistFormView.swift; sourceTree = "<group>"; };
|
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistFormView.swift; sourceTree = "<group>"; };
|
||||||
373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToPlaylistView.swift; sourceTree = "<group>"; };
|
373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToPlaylistView.swift; sourceTree = "<group>"; };
|
||||||
3741B52F2676213400125C5E /* PlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = "<group>"; };
|
|
||||||
3755A0C1269B772000F67988 /* StreamAVPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamAVPlayerViewController.swift; sourceTree = "<group>"; };
|
3755A0C1269B772000F67988 /* StreamAVPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamAVPlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
376578842685429C00D4EA09 /* CaseIterable+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Next.swift"; sourceTree = "<group>"; };
|
376578842685429C00D4EA09 /* CaseIterable+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Next.swift"; sourceTree = "<group>"; };
|
||||||
376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; };
|
376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; };
|
||||||
@ -272,6 +273,11 @@
|
|||||||
37BD07B42698AA4D003EBB87 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
37BD07B42698AA4D003EBB87 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarNavigation.swift; sourceTree = "<group>"; };
|
37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarNavigation.swift; sourceTree = "<group>"; };
|
||||||
37BD07C42698ADEE003EBB87 /* Pearvidious.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Pearvidious.entitlements; sourceTree = "<group>"; };
|
37BD07C42698ADEE003EBB87 /* Pearvidious.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Pearvidious.entitlements; sourceTree = "<group>"; };
|
||||||
|
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
|
||||||
|
37BE0BD226A1D4780092E2DB /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
|
||||||
|
37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
37BE0BD926A214630092E2DB /* PlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
37BE0BDB26A2367F0092E2DB /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
|
||||||
37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockSegment.swift; sourceTree = "<group>"; };
|
37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockSegment.swift; sourceTree = "<group>"; };
|
||||||
37C7A1DB267CE9D90010EAD6 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; };
|
37C7A1DB267CE9D90010EAD6 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; };
|
||||||
37CEE4B42677B628005A1EFE /* StreamType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamType.swift; sourceTree = "<group>"; };
|
37CEE4B42677B628005A1EFE /* StreamType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamType.swift; sourceTree = "<group>"; };
|
||||||
@ -291,7 +297,6 @@
|
|||||||
37D4B15E267164AF00C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
37D4B15E267164AF00C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
37D4B171267164B000C925CA /* Tests Apple TV.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests Apple TV.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
37D4B171267164B000C925CA /* Tests Apple TV.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests Apple TV.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
37D4B175267164B000C925CA /* PearvidiousUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearvidiousUITests.swift; sourceTree = "<group>"; };
|
37D4B175267164B000C925CA /* PearvidiousUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearvidiousUITests.swift; sourceTree = "<group>"; };
|
||||||
37D4B1822671681B00C925CA /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
|
|
||||||
37D4B18B26717B3800C925CA /* VideoListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoListRowView.swift; sourceTree = "<group>"; };
|
37D4B18B26717B3800C925CA /* VideoListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoListRowView.swift; sourceTree = "<group>"; };
|
||||||
37D4B19626717E1500C925CA /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = "<group>"; };
|
37D4B19626717E1500C925CA /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = "<group>"; };
|
||||||
37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@ -377,6 +382,15 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
37BE0BD826A214500092E2DB /* Mac */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
37BE0BDB26A2367F0092E2DB /* Player.swift */,
|
||||||
|
37BE0BD926A214630092E2DB /* PlayerViewController.swift */,
|
||||||
|
);
|
||||||
|
path = Mac;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
37C7A9022679058300E721B4 /* Extensions */ = {
|
37C7A9022679058300E721B4 /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -391,6 +405,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
37D4B0C12671614700C925CA /* Shared */,
|
37D4B0C12671614700C925CA /* Shared */,
|
||||||
|
37BE0BD826A214500092E2DB /* Mac */,
|
||||||
37D4B1B72672CFE300C925CA /* Model */,
|
37D4B1B72672CFE300C925CA /* Model */,
|
||||||
37D4B159267164AE00C925CA /* Apple TV */,
|
37D4B159267164AE00C925CA /* Apple TV */,
|
||||||
37C7A9022679058300E721B4 /* Extensions */,
|
37C7A9022679058300E721B4 /* Extensions */,
|
||||||
@ -411,7 +426,10 @@
|
|||||||
372915E52687E3B900F5A35B /* Defaults.swift */,
|
372915E52687E3B900F5A35B /* Defaults.swift */,
|
||||||
372915E92687EBA500F5A35B /* ListingLayout.swift */,
|
372915E92687EBA500F5A35B /* ListingLayout.swift */,
|
||||||
37D4B0C22671614700C925CA /* PearvidiousApp.swift */,
|
37D4B0C22671614700C925CA /* PearvidiousApp.swift */,
|
||||||
|
37BE0BD226A1D4780092E2DB /* Player.swift */,
|
||||||
|
37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */,
|
||||||
37AAF2932674086B007FC770 /* TabSelection.swift */,
|
37AAF2932674086B007FC770 /* TabSelection.swift */,
|
||||||
|
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */,
|
||||||
37D4B0C42671614800C925CA /* Assets.xcassets */,
|
37D4B0C42671614800C925CA /* Assets.xcassets */,
|
||||||
37BD07C42698ADEE003EBB87 /* Pearvidious.entitlements */,
|
37BD07C42698ADEE003EBB87 /* Pearvidious.entitlements */,
|
||||||
);
|
);
|
||||||
@ -456,8 +474,6 @@
|
|||||||
373CFAC126966159003CB2C6 /* CoverSectionRowView.swift */,
|
373CFAC126966159003CB2C6 /* CoverSectionRowView.swift */,
|
||||||
373CFABD26966115003CB2C6 /* CoverSectionView.swift */,
|
373CFABD26966115003CB2C6 /* CoverSectionView.swift */,
|
||||||
37B76E95268747C900CE5671 /* OptionsView.swift */,
|
37B76E95268747C900CE5671 /* OptionsView.swift */,
|
||||||
37D4B1822671681B00C925CA /* PlayerView.swift */,
|
|
||||||
3741B52F2676213400125C5E /* PlayerViewController.swift */,
|
|
||||||
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */,
|
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */,
|
||||||
376578902685490700D4EA09 /* PlaylistsView.swift */,
|
376578902685490700D4EA09 /* PlaylistsView.swift */,
|
||||||
37AAF27D26737323007FC770 /* PopularVideosView.swift */,
|
37AAF27D26737323007FC770 /* PopularVideosView.swift */,
|
||||||
@ -472,6 +488,7 @@
|
|||||||
37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */,
|
37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */,
|
||||||
37B17DA3268A285E006AEE9B /* VideoDetailsView.swift */,
|
37B17DA3268A285E006AEE9B /* VideoDetailsView.swift */,
|
||||||
37D4B18B26717B3800C925CA /* VideoListRowView.swift */,
|
37D4B18B26717B3800C925CA /* VideoListRowView.swift */,
|
||||||
|
372F954926A4D0C900502766 /* VideoLoading.swift */,
|
||||||
37F4AE7126828F0900BD60EA /* VideosCellsView.swift */,
|
37F4AE7126828F0900BD60EA /* VideosCellsView.swift */,
|
||||||
37AAF29926740A01007FC770 /* VideosListView.swift */,
|
37AAF29926740A01007FC770 /* VideosListView.swift */,
|
||||||
371231832683E62F0000B307 /* VideosView.swift */,
|
371231832683E62F0000B307 /* VideosView.swift */,
|
||||||
@ -529,7 +546,6 @@
|
|||||||
37D4B0C52671614900C925CA /* Sources */,
|
37D4B0C52671614900C925CA /* Sources */,
|
||||||
37D4B0C62671614900C925CA /* Frameworks */,
|
37D4B0C62671614900C925CA /* Frameworks */,
|
||||||
37D4B0C72671614900C925CA /* Resources */,
|
37D4B0C72671614900C925CA /* Resources */,
|
||||||
37BAB54A269B308600E75ED1 /* Run Swiftformat */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -770,27 +786,6 @@
|
|||||||
};
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
|
||||||
37BAB54A269B308600E75ED1 /* Run Swiftformat */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "Run Swiftformat";
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "if which swiftformat >/dev/null; then\n swiftformat .\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n";
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
37D4B0C52671614900C925CA /* Sources */ = {
|
37D4B0C52671614900C925CA /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
@ -802,8 +797,9 @@
|
|||||||
37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */,
|
37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */,
|
||||||
37F4AE762682908700BD60EA /* VideoCellView.swift in Sources */,
|
37F4AE762682908700BD60EA /* VideoCellView.swift in Sources */,
|
||||||
37C7A1DA267CACF50010EAD6 /* TrendingCountrySelectionView.swift in Sources */,
|
37C7A1DA267CACF50010EAD6 /* TrendingCountrySelectionView.swift in Sources */,
|
||||||
377FC7E6267A085600A6BBAF /* PlayerView.swift in Sources */,
|
|
||||||
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
||||||
|
37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */,
|
||||||
|
37BE0BD326A1D4780092E2DB /* Player.swift in Sources */,
|
||||||
37CEE4C12677B697005A1EFE /* Stream.swift in Sources */,
|
37CEE4C12677B697005A1EFE /* Stream.swift in Sources */,
|
||||||
37F4AE7226828F0900BD60EA /* VideosCellsView.swift in Sources */,
|
37F4AE7226828F0900BD60EA /* VideosCellsView.swift in Sources */,
|
||||||
373CFAE326974812003CB2C6 /* PlaylistVisibility.swift in Sources */,
|
373CFAE326974812003CB2C6 /* PlaylistVisibility.swift in Sources */,
|
||||||
@ -823,7 +819,6 @@
|
|||||||
377FC7E3267A084A00A6BBAF /* VideoListRowView.swift in Sources */,
|
377FC7E3267A084A00A6BBAF /* VideoListRowView.swift in Sources */,
|
||||||
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
||||||
37AAF2942674086B007FC770 /* TabSelection.swift in Sources */,
|
37AAF2942674086B007FC770 /* TabSelection.swift in Sources */,
|
||||||
377FC7E9267A085D00A6BBAF /* PlayerViewController.swift in Sources */,
|
|
||||||
377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */,
|
377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */,
|
||||||
376578912685490700D4EA09 /* PlaylistsView.swift in Sources */,
|
376578912685490700D4EA09 /* PlaylistsView.swift in Sources */,
|
||||||
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
||||||
@ -834,7 +829,6 @@
|
|||||||
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||||
37B767DB2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
37B767DB2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
||||||
373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||||
372915EA2687EBA500F5A35B /* ListingLayout.swift in Sources */,
|
|
||||||
373CFAC226966159003CB2C6 /* CoverSectionRowView.swift in Sources */,
|
373CFAC226966159003CB2C6 /* CoverSectionRowView.swift in Sources */,
|
||||||
37141673267A8E10006CA35D /* Country.swift in Sources */,
|
37141673267A8E10006CA35D /* Country.swift in Sources */,
|
||||||
37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
||||||
@ -846,7 +840,7 @@
|
|||||||
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||||
37D4B19726717E1500C925CA /* Video.swift in Sources */,
|
37D4B19726717E1500C925CA /* Video.swift in Sources */,
|
||||||
371F2F1A269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
371F2F1A269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
||||||
37B76E96268747C900CE5671 /* OptionsView.swift in Sources */,
|
37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||||
37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
||||||
37D4B0E42671614900C925CA /* PearvidiousApp.swift in Sources */,
|
37D4B0E42671614900C925CA /* PearvidiousApp.swift in Sources */,
|
||||||
37CEE4B92677B63F005A1EFE /* StreamResolution.swift in Sources */,
|
37CEE4B92677B63F005A1EFE /* StreamResolution.swift in Sources */,
|
||||||
@ -858,6 +852,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */,
|
||||||
37CEE4BE2677B670005A1EFE /* AudioVideoStream.swift in Sources */,
|
37CEE4BE2677B670005A1EFE /* AudioVideoStream.swift in Sources */,
|
||||||
37D80702269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */,
|
37D80702269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */,
|
||||||
37F4AE772682908700BD60EA /* VideoCellView.swift in Sources */,
|
37F4AE772682908700BD60EA /* VideoCellView.swift in Sources */,
|
||||||
@ -865,8 +860,6 @@
|
|||||||
37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||||
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
|
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
|
||||||
371F2F1B269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
371F2F1B269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
||||||
372915EB2687EBA500F5A35B /* ListingLayout.swift in Sources */,
|
|
||||||
3774063826999F7C00304C93 /* PlayerView.swift in Sources */,
|
|
||||||
377FC7DD267A081A00A6BBAF /* PopularVideosView.swift in Sources */,
|
377FC7DD267A081A00A6BBAF /* PopularVideosView.swift in Sources */,
|
||||||
3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||||
37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */,
|
37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */,
|
||||||
@ -890,7 +883,7 @@
|
|||||||
37F4AE7326828F0900BD60EA /* VideosCellsView.swift in Sources */,
|
37F4AE7326828F0900BD60EA /* VideosCellsView.swift in Sources */,
|
||||||
377FC7E0267A082600A6BBAF /* ChannelView.swift in Sources */,
|
377FC7E0267A082600A6BBAF /* ChannelView.swift in Sources */,
|
||||||
379775942689365600DD52A8 /* Array+Next.swift in Sources */,
|
379775942689365600DD52A8 /* Array+Next.swift in Sources */,
|
||||||
37B76E97268747C900CE5671 /* OptionsView.swift in Sources */,
|
37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */,
|
||||||
371231862683E7820000B307 /* VideosView.swift in Sources */,
|
371231862683E7820000B307 /* VideosView.swift in Sources */,
|
||||||
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||||
373CFAD026966290003CB2C6 /* SearchSortOrder.swift in Sources */,
|
373CFAD026966290003CB2C6 /* SearchSortOrder.swift in Sources */,
|
||||||
@ -903,6 +896,7 @@
|
|||||||
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
||||||
37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
|
37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
|
||||||
37BD07C12698AD3B003EBB87 /* TrendingCountrySelectionView.swift in Sources */,
|
37BD07C12698AD3B003EBB87 /* TrendingCountrySelectionView.swift in Sources */,
|
||||||
|
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||||
373CFAE426974812003CB2C6 /* PlaylistVisibility.swift in Sources */,
|
373CFAE426974812003CB2C6 /* PlaylistVisibility.swift in Sources */,
|
||||||
37CEE4BA2677B63F005A1EFE /* StreamResolution.swift in Sources */,
|
37CEE4BA2677B63F005A1EFE /* StreamResolution.swift in Sources */,
|
||||||
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
||||||
@ -939,11 +933,11 @@
|
|||||||
37CEE4BF2677B670005A1EFE /* AudioVideoStream.swift in Sources */,
|
37CEE4BF2677B670005A1EFE /* AudioVideoStream.swift in Sources */,
|
||||||
37CEE4B72677B628005A1EFE /* StreamType.swift in Sources */,
|
37CEE4B72677B628005A1EFE /* StreamType.swift in Sources */,
|
||||||
37F4AE782682908700BD60EA /* VideoCellView.swift in Sources */,
|
37F4AE782682908700BD60EA /* VideoCellView.swift in Sources */,
|
||||||
|
37BE0BD426A1D47D0092E2DB /* Player.swift in Sources */,
|
||||||
37977585268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
37977585268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
||||||
37F4AE7426828F0900BD60EA /* VideosCellsView.swift in Sources */,
|
37F4AE7426828F0900BD60EA /* VideosCellsView.swift in Sources */,
|
||||||
376578872685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
376578872685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
||||||
373CFAE526974812003CB2C6 /* PlaylistVisibility.swift in Sources */,
|
373CFAE526974812003CB2C6 /* PlaylistVisibility.swift in Sources */,
|
||||||
37D4B1842671684E00C925CA /* PlayerView.swift in Sources */,
|
|
||||||
37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */,
|
37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */,
|
||||||
371231852683E7820000B307 /* VideosView.swift in Sources */,
|
371231852683E7820000B307 /* VideosView.swift in Sources */,
|
||||||
37BD07CA2698FBE5003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
37BD07CA2698FBE5003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
||||||
@ -957,11 +951,11 @@
|
|||||||
373CFADD269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
373CFADD269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||||
3755A0C2269B772000F67988 /* StreamAVPlayerViewController.swift in Sources */,
|
3755A0C2269B772000F67988 /* StreamAVPlayerViewController.swift in Sources */,
|
||||||
37C7A1DE267CE9D90010EAD6 /* Profile.swift in Sources */,
|
37C7A1DE267CE9D90010EAD6 /* Profile.swift in Sources */,
|
||||||
3741B5302676213400125C5E /* PlayerViewController.swift in Sources */,
|
|
||||||
373CFABE26966148003CB2C6 /* CoverSectionView.swift in Sources */,
|
373CFABE26966148003CB2C6 /* CoverSectionView.swift in Sources */,
|
||||||
37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
||||||
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||||
37D4B18E26717B3800C925CA /* VideoListRowView.swift in Sources */,
|
37D4B18E26717B3800C925CA /* VideoListRowView.swift in Sources */,
|
||||||
|
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||||
37AAF27E26737323007FC770 /* PopularVideosView.swift in Sources */,
|
37AAF27E26737323007FC770 /* PopularVideosView.swift in Sources */,
|
||||||
37AAF29A26740A01007FC770 /* VideosListView.swift in Sources */,
|
37AAF29A26740A01007FC770 /* VideosListView.swift in Sources */,
|
||||||
37D80703269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */,
|
37D80703269C74F8002ECBBA /* ThumbnailQuality.swift in Sources */,
|
||||||
@ -971,7 +965,10 @@
|
|||||||
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
||||||
371F2F1C269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
371F2F1C269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
||||||
37B17DA0268A1F89006AEE9B /* VideoContextMenuView.swift in Sources */,
|
37B17DA0268A1F89006AEE9B /* VideoContextMenuView.swift in Sources */,
|
||||||
|
37BE0BD726A1D4A90092E2DB /* PlayerViewController.swift in Sources */,
|
||||||
|
372F954A26A4D27000502766 /* VideoLoading.swift in Sources */,
|
||||||
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
|
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
|
||||||
|
37BE0BE526A336910092E2DB /* OptionsView.swift in Sources */,
|
||||||
373CFAD5269662AB003CB2C6 /* SearchDate.swift in Sources */,
|
373CFAD5269662AB003CB2C6 /* SearchDate.swift in Sources */,
|
||||||
379775952689365600DD52A8 /* Array+Next.swift in Sources */,
|
379775952689365600DD52A8 /* Array+Next.swift in Sources */,
|
||||||
37AAF28A2673AB89007FC770 /* ChannelView.swift in Sources */,
|
37AAF28A2673AB89007FC770 /* ChannelView.swift in Sources */,
|
||||||
@ -989,7 +986,6 @@
|
|||||||
372915E82687E3B900F5A35B /* Defaults.swift in Sources */,
|
372915E82687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||||
37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */,
|
37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */,
|
||||||
37D4B1812671653A00C925CA /* AppTabNavigation.swift in Sources */,
|
37D4B1812671653A00C925CA /* AppTabNavigation.swift in Sources */,
|
||||||
37B76E98268747C900CE5671 /* OptionsView.swift in Sources */,
|
|
||||||
37CEE4BB2677B63F005A1EFE /* StreamResolution.swift in Sources */,
|
37CEE4BB2677B63F005A1EFE /* StreamResolution.swift in Sources */,
|
||||||
3797758D2689345500DD52A8 /* Store.swift in Sources */,
|
3797758D2689345500DD52A8 /* Store.swift in Sources */,
|
||||||
);
|
);
|
||||||
@ -1212,6 +1208,7 @@
|
|||||||
ENABLE_USER_SELECTED_FILES = readonly;
|
ENABLE_USER_SELECTED_FILES = readonly;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
@ -1242,6 +1239,7 @@
|
|||||||
ENABLE_USER_SELECTED_FILES = readonly;
|
ENABLE_USER_SELECTED_FILES = readonly;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
|
@ -3,102 +3,4 @@
|
|||||||
uuid = "E30DA302-B258-4C14-8808-5E4CE238A4FF"
|
uuid = "E30DA302-B258-4C14-8808-5E4CE238A4FF"
|
||||||
type = "1"
|
type = "1"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<Breakpoints>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "032F98C7-06A6-4DC6-8913-771A5D007CE5"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Apple TV/SearchView.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "33"
|
|
||||||
endingLineNumber = "33"
|
|
||||||
landmarkName = "body"
|
|
||||||
landmarkType = "24">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "FCFDD746-6F29-43D8-B813-CCDFF1B5AE90"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Model/InvidiousAPI.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "59"
|
|
||||||
endingLineNumber = "59"
|
|
||||||
landmarkName = "init()"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "E59B76C5-4001-4E08-92DC-A345E7954D4D"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Apple TV/NewPlaylistView.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "32"
|
|
||||||
endingLineNumber = "32"
|
|
||||||
landmarkName = "body"
|
|
||||||
landmarkType = "24">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "2B9757FC-6E4B-4236-9E5D-48DB8534C673"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Apple TV/PlaylistFormView.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "75"
|
|
||||||
endingLineNumber = "75"
|
|
||||||
landmarkName = "submitForm()"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "69B09B52-FD39-4B67-A3C1-1344483D1C9F"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Apple TV/PlaylistFormView.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "76"
|
|
||||||
endingLineNumber = "76"
|
|
||||||
landmarkName = "submitForm()"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "E7DD4BAD-57BC-472C-91FD-EA6E144CB840"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Apple TV/PlayerViewController.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "100"
|
|
||||||
endingLineNumber = "100"
|
|
||||||
landmarkName = "updateUIViewController(_:context:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
</Breakpoints>
|
|
||||||
</Bucket>
|
</Bucket>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import Defaults
|
import Defaults
|
||||||
|
|
||||||
extension Defaults.Keys {
|
extension Defaults.Keys {
|
||||||
static let layout = Key<ListingLayout>("listingLayout", default: .cells)
|
#if os(tvOS)
|
||||||
|
static let layout = Key<ListingLayout>("listingLayout", default: .cells)
|
||||||
|
#endif
|
||||||
static let searchQuery = Key<String>("searchQuery", default: "")
|
static let searchQuery = Key<String>("searchQuery", default: "")
|
||||||
|
|
||||||
static let searchSortOrder = Key<SearchSortOrder>("searchSortOrder", default: .relevance)
|
static let searchSortOrder = Key<SearchSortOrder>("searchSortOrder", default: .relevance)
|
||||||
|
14
Shared/Player.swift
Normal file
14
Shared/Player.swift
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct Player: UIViewControllerRepresentable {
|
||||||
|
var video: Video?
|
||||||
|
|
||||||
|
func makeUIViewController(context _: Context) -> PlayerViewController {
|
||||||
|
let controller = PlayerViewController()
|
||||||
|
controller.video = video
|
||||||
|
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_: PlayerViewController, context _: Context) {}
|
||||||
|
}
|
81
Shared/PlayerViewController.swift
Normal file
81
Shared/PlayerViewController.swift
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import AVKit
|
||||||
|
import Logging
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
final class PlayerViewController: UIViewController {
|
||||||
|
var video: Video!
|
||||||
|
|
||||||
|
var playerLoaded = false
|
||||||
|
var playingFullScreen = false
|
||||||
|
|
||||||
|
var player = AVPlayer()
|
||||||
|
var playerState: PlayerState! = PlayerState()
|
||||||
|
var playerViewController = AVPlayerViewController()
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
if !playerLoaded {
|
||||||
|
loadPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
|
||||||
|
try? AVAudioSession.sharedInstance().setActive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
#if os(iOS)
|
||||||
|
if !playingFullScreen {
|
||||||
|
playerViewController.player?.replaceCurrentItem(with: nil)
|
||||||
|
playerViewController.player = nil
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPlayer() {
|
||||||
|
playerState.player = player
|
||||||
|
playerViewController.player = playerState.player
|
||||||
|
playerState.loadVideo(video)
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
present(playerViewController, animated: false)
|
||||||
|
#else
|
||||||
|
playerViewController.exitsFullScreenWhenPlaybackEnds = true
|
||||||
|
playerViewController.view.frame = view.bounds
|
||||||
|
|
||||||
|
addChild(playerViewController)
|
||||||
|
view.addSubview(playerViewController.view)
|
||||||
|
|
||||||
|
playerViewController.didMove(toParent: self)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
playerViewController.delegate = self
|
||||||
|
playerLoaded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlayerViewController: AVPlayerViewControllerDelegate {
|
||||||
|
func playerViewControllerShouldDismiss(_: AVPlayerViewController) -> Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) {
|
||||||
|
dismiss(animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewController(
|
||||||
|
_: AVPlayerViewController,
|
||||||
|
willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
|
||||||
|
) {
|
||||||
|
playingFullScreen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerViewController(
|
||||||
|
_: AVPlayerViewController,
|
||||||
|
willEndFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
|
||||||
|
) {
|
||||||
|
playingFullScreen = false
|
||||||
|
}
|
||||||
|
}
|
65
Shared/VideoPlayerView.swift
Normal file
65
Shared/VideoPlayerView.swift
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import AVKit
|
||||||
|
import Siesta
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct VideoPlayerView: View {
|
||||||
|
@EnvironmentObject<NavigationState> private var navigationState
|
||||||
|
|
||||||
|
@ObservedObject private var store = Store<Video>()
|
||||||
|
|
||||||
|
var resource: Resource {
|
||||||
|
InvidiousAPI.shared.video(video.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var video: Video
|
||||||
|
|
||||||
|
var player: AVPlayer!
|
||||||
|
|
||||||
|
init(_ video: Video) {
|
||||||
|
self.video = video
|
||||||
|
resource.addObserver(store)
|
||||||
|
|
||||||
|
player = AVPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
#if os(tvOS)
|
||||||
|
if store.item == nil {
|
||||||
|
VideoLoading(video: video)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
Player(video: video)
|
||||||
|
.frame(alignment: .leading)
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
ScrollView(.vertical) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(video.title)
|
||||||
|
Text(video.author)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
resource.loadIfNeeded()
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
resource.removeObservers(ownedBy: store)
|
||||||
|
resource.invalidate()
|
||||||
|
|
||||||
|
navigationState.showingVideoDetails = navigationState.returnToDetails
|
||||||
|
}
|
||||||
|
#if os(tvOS)
|
||||||
|
.background(.thinMaterial)
|
||||||
|
#elseif os(macOS)
|
||||||
|
.navigationTitle(video.title)
|
||||||
|
#elseif os(iOS)
|
||||||
|
.navigationBarTitle(video.title, displayMode: .inline)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user