mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2024-12-04 04:01:47 +00:00
Fix tvOS gestures. Start implementing forced focus.
Make views scroll to top when focusing on top most button. Dumb swift.
This commit is contained in:
parent
f373a71e89
commit
24f358d781
@ -43,7 +43,7 @@ struct LandscapeItemElement: View {
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ImageView(src: (item.type == "Episode" ? item.getSeriesBackdropImage(maxWidth: 800) : item.getBackdropImage(maxWidth: 800)), bh: item.type == "Episode" ? item.getSeriesBackdropImageBlurHash() : item.getBackdropImageBlurHash())
|
||||
ImageView(src: (item.type == "Episode" ? item.getSeriesBackdropImage(maxWidth: 445) : item.getBackdropImage(maxWidth: 445)), bh: item.type == "Episode" ? item.getSeriesBackdropImageBlurHash() : item.getBackdropImageBlurHash())
|
||||
.frame(width: 445, height: 250)
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
|
37
JellyfinPlayer tvOS/Components/MediaViewActionButton.swift
Normal file
37
JellyfinPlayer tvOS/Components/MediaViewActionButton.swift
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
/*
|
||||
* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MediaViewActionButton: View {
|
||||
@Environment(\.isFocused) var envFocused: Bool
|
||||
@State var focused: Bool = false
|
||||
var icon: String
|
||||
@Binding var scrollView: UIScrollView?
|
||||
var iconColor: Color?
|
||||
|
||||
var body: some View {
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(focused ? .black : iconColor ?? .white)
|
||||
.onChange(of: envFocused) { envFocus in
|
||||
if(envFocus == true) {
|
||||
scrollView?.scrollToTop()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
scrollView?.scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
withAnimation(.linear(duration: 0.15)) {
|
||||
self.focused = envFocus
|
||||
}
|
||||
}
|
||||
.font(.system(size: 40))
|
||||
.padding(.vertical, 12).padding(.horizontal, 20)
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ struct PortraitItemElement: View {
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ImageView(src: item.type == "Episode" ? item.getSeriesPrimaryImage(maxWidth: 400) : item.getPrimaryImage(maxWidth: 400), bh: item.type == "Episode" ? item.getSeriesPrimaryImageBlurHash() : item.getPrimaryImageBlurHash())
|
||||
ImageView(src: item.type == "Episode" ? item.getSeriesPrimaryImage(maxWidth: 200) : item.getPrimaryImage(maxWidth: 200), bh: item.type == "Episode" ? item.getSeriesPrimaryImageBlurHash() : item.getPrimaryImageBlurHash())
|
||||
.frame(width: 200, height: 300)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: focused ? 10.0 : 0)
|
||||
|
@ -42,9 +42,11 @@ struct LatestMediaView: View {
|
||||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: LazyView { ItemView(item: item) }) {
|
||||
PortraitItemElement(item: item)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
if item.type == "Series" || item.type == "Movie" {
|
||||
NavigationLink(destination: LazyView { ItemView(item: item) }) {
|
||||
PortraitItemElement(item: item)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
}
|
||||
Spacer().frame(width: 45)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
import SwiftUIFocusGuide
|
||||
|
||||
struct MovieItemView: View {
|
||||
@ObservedObject var viewModel: MovieItemViewModel
|
||||
@ -17,6 +18,10 @@ struct MovieItemView: View {
|
||||
@State var studio: String? = nil;
|
||||
@State var director: String? = nil;
|
||||
|
||||
@State var wrappedScrollView: UIScrollView?;
|
||||
|
||||
@StateObject var focusBag = SwiftUIFocusBag()
|
||||
|
||||
@Namespace private var namespace
|
||||
|
||||
func onAppear() {
|
||||
@ -45,9 +50,6 @@ struct MovieItemView: View {
|
||||
.opacity(0.4)
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
Spacer() //i hate ficus engine
|
||||
.frame(width: 1920, height: 2)
|
||||
.focusable()
|
||||
Text(viewModel.item.name ?? "")
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
@ -132,19 +134,14 @@ struct MovieItemView: View {
|
||||
Button {
|
||||
viewModel.updateFavoriteState()
|
||||
} label: {
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundColor(viewModel.isFavorited ? .red : .primary)
|
||||
.font(.system(size: 40))
|
||||
.padding(.vertical, 12).padding(.horizontal, 20)
|
||||
MediaViewActionButton(icon: "heart.fill", scrollView: $wrappedScrollView, iconColor: viewModel.isFavorited ? .red : .white)
|
||||
}
|
||||
Text(viewModel.isFavorited ? "Unfavorite" : "Favorite")
|
||||
.font(.caption)
|
||||
}
|
||||
VStack {
|
||||
NavigationLink(destination: VideoPlayerView(item: viewModel.item)) {
|
||||
Image(systemName: "play.fill")
|
||||
.font(.system(size: 40))
|
||||
.padding(.vertical, 12).padding(.horizontal, 20)
|
||||
MediaViewActionButton(icon: "play.fill", scrollView: $wrappedScrollView)
|
||||
}
|
||||
Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString()) left" : "Play")
|
||||
.font(.caption)
|
||||
@ -153,19 +150,18 @@ struct MovieItemView: View {
|
||||
Button {
|
||||
viewModel.updateWatchState()
|
||||
} label: {
|
||||
Image(systemName: "eye.fill")
|
||||
.foregroundColor(viewModel.isWatched ? .red : .primary)
|
||||
.font(.system(size: 40))
|
||||
.padding(.vertical, 12).padding(.horizontal, 20)
|
||||
MediaViewActionButton(icon: "eye.fill", scrollView: $wrappedScrollView, iconColor: viewModel.isWatched ? .red : .white)
|
||||
}
|
||||
Text(viewModel.isWatched ? "Unwatch" : "Mark Watched")
|
||||
.font(.caption)
|
||||
}
|
||||
}.padding(.top, 15)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 15)
|
||||
.addFocusGuide(using: focusBag, name: "actionButtons", destinations: [.bottom: "moreLikeThis"], debug: true)
|
||||
}
|
||||
}.padding(.top, 50)
|
||||
|
||||
|
||||
if(!viewModel.similarItems.isEmpty) {
|
||||
Text("More Like This")
|
||||
.font(.headline)
|
||||
@ -181,11 +177,21 @@ struct MovieItemView: View {
|
||||
Spacer().frame(width: 45)
|
||||
}
|
||||
}.padding(EdgeInsets(top: -30, leading: -90, bottom: 0, trailing: -90))
|
||||
.addFocusGuide(using: focusBag, name: "moreLikeThis", destinations: [.top: "actionButtons"], debug: false)
|
||||
.frame(height: 360)
|
||||
}
|
||||
}.padding(EdgeInsets(top: 90, leading: 90, bottom: 0, trailing: 90))
|
||||
}.introspectScrollView { scrollView in
|
||||
wrappedScrollView = scrollView
|
||||
}
|
||||
}.onAppear(perform: onAppear)
|
||||
.focusScope(namespace)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIScrollView {
|
||||
func scrollToTop() {
|
||||
let desiredOffset = CGPoint(x: 0, y: 0)
|
||||
setContentOffset(desiredOffset, animated: true)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
import SwiftUIFocusGuide
|
||||
|
||||
struct SeriesItemView: View {
|
||||
@ObservedObject var viewModel: SeriesItemViewModel
|
||||
@ -17,6 +18,10 @@ struct SeriesItemView: View {
|
||||
@State var studio: String? = nil;
|
||||
@State var director: String? = nil;
|
||||
|
||||
@State var wrappedScrollView: UIScrollView?;
|
||||
|
||||
@StateObject var focusBag = SwiftUIFocusBag()
|
||||
|
||||
@Environment(\.resetFocus) var resetFocus
|
||||
@Namespace private var namespace
|
||||
|
||||
@ -45,183 +50,173 @@ struct SeriesItemView: View {
|
||||
ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash())
|
||||
.opacity(0.4)
|
||||
ScrollView {
|
||||
ScrollViewReader { reader in
|
||||
LazyVStack(alignment: .leading) {
|
||||
Spacer() //i hate ficus engine
|
||||
.frame(width: 1920, height: 2)
|
||||
.focusable()
|
||||
Text(viewModel.item.name ?? "")
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.primary)
|
||||
HStack {
|
||||
Text(viewModel.getRunYears()).font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
LazyVStack(alignment: .leading) {
|
||||
Text(viewModel.item.name ?? "")
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.primary)
|
||||
HStack {
|
||||
Text(viewModel.getRunYears()).font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
if viewModel.item.officialRating != nil {
|
||||
Text(viewModel.item.officialRating!).font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
if viewModel.item.officialRating != nil {
|
||||
Text(viewModel.item.officialRating!).font(.subheadline)
|
||||
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
||||
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||
.stroke(Color.secondary, lineWidth: 1))
|
||||
}
|
||||
if viewModel.item.communityRating != nil {
|
||||
HStack {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
.font(.subheadline)
|
||||
Text(String(viewModel.item.communityRating!)).font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
||||
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||
.stroke(Color.secondary, lineWidth: 1))
|
||||
}
|
||||
if viewModel.item.communityRating != nil {
|
||||
HStack {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
.font(.subheadline)
|
||||
Text(String(viewModel.item.communityRating!)).font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .trailing) {
|
||||
if(studio != nil) {
|
||||
Text("STUDIO")
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
Text(studio!)
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
|
||||
if(director != nil) {
|
||||
Text("DIRECTOR")
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
Text(director!)
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
|
||||
if(!actors.isEmpty) {
|
||||
Text("CAST")
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
ForEach(actors, id: \.id) { person in
|
||||
Text(person.name!)
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
if(!(viewModel.item.taglines ?? []).isEmpty) {
|
||||
Text(viewModel.item.taglines?.first ?? "")
|
||||
.font(.body)
|
||||
.italic()
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
Text(viewModel.item.overview ?? "")
|
||||
}
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .trailing) {
|
||||
if(studio != nil) {
|
||||
Text("STUDIO")
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
Text(studio!)
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
|
||||
if(director != nil) {
|
||||
Text("DIRECTOR")
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
Text(director!)
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
|
||||
if(!actors.isEmpty) {
|
||||
Text("CAST")
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
ForEach(actors, id: \.id) { person in
|
||||
Text(person.name!)
|
||||
.font(.body)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
if(!(viewModel.item.taglines ?? []).isEmpty) {
|
||||
Text(viewModel.item.taglines?.first ?? "")
|
||||
.font(.body)
|
||||
.italic()
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
HStack {
|
||||
VStack {
|
||||
Button {
|
||||
viewModel.updateFavoriteState()
|
||||
} label: {
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundColor(viewModel.isFavorited ? .red : .primary)
|
||||
.font(.system(size: 40))
|
||||
.padding(.vertical, 12).padding(.horizontal, 20)
|
||||
}.prefersDefaultFocus(in: namespace)
|
||||
Text(viewModel.isFavorited ? "Unfavorite" : "Favorite")
|
||||
.font(.caption)
|
||||
}
|
||||
if(viewModel.nextUpItem != nil) {
|
||||
VStack {
|
||||
NavigationLink(destination: VideoPlayerView(item: viewModel.nextUpItem!)) {
|
||||
Image(systemName: "play.fill")
|
||||
.font(.system(size: 40))
|
||||
.padding(.vertical, 12).padding(.horizontal, 20)
|
||||
}
|
||||
Text("Play • \(viewModel.nextUpItem!.getEpisodeLocator())")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
VStack {
|
||||
Button {
|
||||
viewModel.updateWatchState()
|
||||
} label: {
|
||||
Image(systemName: "eye.fill")
|
||||
.foregroundColor(viewModel.isWatched ? .red : .primary)
|
||||
.font(.system(size: 40))
|
||||
.padding(.vertical, 12).padding(.horizontal, 20)
|
||||
}
|
||||
Text(viewModel.isWatched ? "Unwatch" : "Mark Watched")
|
||||
.font(.caption)
|
||||
}
|
||||
}.padding(.top, 15)
|
||||
Spacer()
|
||||
}
|
||||
}.padding(.top, 50)
|
||||
|
||||
if(viewModel.nextUpItem != nil) {
|
||||
Text("Next Up")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
NavigationLink(destination: ItemView(item: viewModel.nextUpItem!)) {
|
||||
LandscapeItemElement(item: viewModel.nextUpItem!)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle()).padding(.bottom, 1)
|
||||
}
|
||||
|
||||
if(!viewModel.seasons.isEmpty) {
|
||||
Text("Seasons")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
ScrollView(.horizontal) {
|
||||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(viewModel.seasons, id: \.id) { season in
|
||||
NavigationLink(destination: ItemView(item: season)) {
|
||||
PortraitItemElement(item: season)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
Spacer().frame(width: 45)
|
||||
Text(viewModel.item.overview ?? "")
|
||||
.font(.body)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
HStack {
|
||||
VStack {
|
||||
Button {
|
||||
viewModel.updateFavoriteState()
|
||||
} label: {
|
||||
MediaViewActionButton(icon: "heart.fill", scrollView: $wrappedScrollView, iconColor: viewModel.isFavorited ? .red : .white)
|
||||
}.prefersDefaultFocus(in: namespace)
|
||||
Text(viewModel.isFavorited ? "Unfavorite" : "Favorite")
|
||||
.font(.caption)
|
||||
}
|
||||
}.padding(EdgeInsets(top: -30, leading: -90, bottom: 0, trailing: -90))
|
||||
.frame(height: 360)
|
||||
}
|
||||
|
||||
if(!viewModel.similarItems.isEmpty) {
|
||||
Text("More Like This")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
ScrollView(.horizontal) {
|
||||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(viewModel.similarItems, id: \.id) { similarItems in
|
||||
NavigationLink(destination: ItemView(item: similarItems)) {
|
||||
PortraitItemElement(item: similarItems)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
if(viewModel.nextUpItem != nil) {
|
||||
VStack {
|
||||
NavigationLink(destination: VideoPlayerView(item: viewModel.nextUpItem!)) {
|
||||
MediaViewActionButton(icon: "play.fill", scrollView: $wrappedScrollView)
|
||||
}
|
||||
Text("Play • \(viewModel.nextUpItem!.getEpisodeLocator())")
|
||||
.font(.caption)
|
||||
}
|
||||
Spacer().frame(width: 45)
|
||||
}
|
||||
}.padding(EdgeInsets(top: -30, leading: -90, bottom: 0, trailing: -90))
|
||||
.frame(height: 360)
|
||||
VStack {
|
||||
Button {
|
||||
viewModel.updateWatchState()
|
||||
} label: {
|
||||
MediaViewActionButton(icon: "eye.fill", scrollView: $wrappedScrollView, iconColor: viewModel.isWatched ? .red : .white)
|
||||
}
|
||||
Text(viewModel.isWatched ? "Unwatch" : "Mark Watched")
|
||||
.font(.caption)
|
||||
}
|
||||
}.padding(.top, 15)
|
||||
Spacer()
|
||||
}
|
||||
}.padding(EdgeInsets(top: 90, leading: 90, bottom: 45, trailing: 90))
|
||||
}
|
||||
}.padding(.top, 50)
|
||||
|
||||
if(viewModel.nextUpItem != nil) {
|
||||
Text("Next Up")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
NavigationLink(destination: ItemView(item: viewModel.nextUpItem!)) {
|
||||
LandscapeItemElement(item: viewModel.nextUpItem!)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle()).padding(.bottom, 1)
|
||||
}
|
||||
|
||||
if(!viewModel.seasons.isEmpty) {
|
||||
Text("Seasons")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
ScrollView(.horizontal) {
|
||||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(viewModel.seasons, id: \.id) { season in
|
||||
NavigationLink(destination: ItemView(item: season)) {
|
||||
PortraitItemElement(item: season)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
Spacer().frame(width: 45)
|
||||
}
|
||||
}.padding(EdgeInsets(top: -30, leading: -90, bottom: 0, trailing: -90))
|
||||
.frame(height: 360)
|
||||
}
|
||||
|
||||
if(!viewModel.similarItems.isEmpty) {
|
||||
Text("More Like This")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
ScrollView(.horizontal) {
|
||||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(viewModel.similarItems, id: \.id) { similarItems in
|
||||
NavigationLink(destination: ItemView(item: similarItems)) {
|
||||
PortraitItemElement(item: similarItems)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
Spacer().frame(width: 45)
|
||||
}
|
||||
}.padding(EdgeInsets(top: -30, leading: -90, bottom: 0, trailing: -90))
|
||||
.frame(height: 360)
|
||||
}
|
||||
}.padding(EdgeInsets(top: 90, leading: 90, bottom: 45, trailing: 90))
|
||||
}.focusScope(namespace)
|
||||
.introspectScrollView { scrollView in
|
||||
wrappedScrollView = scrollView
|
||||
}
|
||||
}.onAppear(perform: onAppear)
|
||||
}
|
||||
}
|
||||
|
@ -49,16 +49,8 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
var lastTime: Float = 0.0
|
||||
var startTime: Int = 0
|
||||
|
||||
var selectedAudioTrack: Int32 = -1 {
|
||||
didSet {
|
||||
print(selectedAudioTrack)
|
||||
}
|
||||
}
|
||||
var selectedCaptionTrack: Int32 = -1 {
|
||||
didSet {
|
||||
print(selectedCaptionTrack)
|
||||
}
|
||||
}
|
||||
var selectedAudioTrack: Int32 = -1
|
||||
var selectedCaptionTrack: Int32 = -1
|
||||
|
||||
var subtitleTrackArray: [Subtitle] = []
|
||||
var audioTrackArray: [AudioTrack] = []
|
||||
@ -240,22 +232,14 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
if let rawStartTicks = manifest.userData?.playbackPositionTicks {
|
||||
mediaPlayer.jumpForward(Int32(rawStartTicks / 10_000_000))
|
||||
}
|
||||
|
||||
// Pause and load captions into memory.
|
||||
mediaPlayer.pause()
|
||||
|
||||
|
||||
subtitleTrackArray.forEach { sub in
|
||||
if sub.id != -1 && sub.delivery == .external {
|
||||
mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false)
|
||||
}
|
||||
}
|
||||
|
||||
// Select default track & resume playback
|
||||
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
|
||||
mediaPlayer.pause()
|
||||
mediaPlayer.play()
|
||||
playing = true
|
||||
|
||||
setupInfoPanel()
|
||||
|
||||
})
|
||||
@ -305,14 +289,9 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
|
||||
if let event = remoteEvent as? MPChangePlaybackPositionCommandEvent {
|
||||
let targetSeconds = event.positionTime
|
||||
print(targetSeconds)
|
||||
|
||||
let videoPosition = Double(self.mediaPlayer.time.intValue / 1000)
|
||||
print(videoPosition)
|
||||
|
||||
let offset = targetSeconds - videoPosition
|
||||
print(offset)
|
||||
|
||||
|
||||
if offset > 0 {
|
||||
self.mediaPlayer.jumpForward(Int32(offset))
|
||||
} else {
|
||||
@ -438,40 +417,44 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: Gestures
|
||||
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
||||
for item in presses {
|
||||
if(item.type == .select) {
|
||||
selectButtonTapped()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupGestures() {
|
||||
|
||||
self.becomeFirstResponder()
|
||||
|
||||
//vlc crap
|
||||
videoContentView.gestureRecognizers?.forEach { gr in
|
||||
videoContentView.removeGestureRecognizer(gr)
|
||||
}
|
||||
videoContentView.subviews.forEach { sv in
|
||||
sv.gestureRecognizers?.forEach { gr in
|
||||
sv.removeGestureRecognizer(gr)
|
||||
}
|
||||
}
|
||||
|
||||
let playPauseGesture = UITapGestureRecognizer(target: self, action: #selector(self.selectButtonTapped))
|
||||
let playPauseType = UIPress.PressType.playPause
|
||||
playPauseGesture.allowedPressTypes = [NSNumber(value: playPauseType.rawValue)]
|
||||
view.addGestureRecognizer(playPauseGesture)
|
||||
|
||||
let selectGesture = UITapGestureRecognizer(target: self, action: #selector(self.selectButtonTapped))
|
||||
let selectType = UIPress.PressType.select
|
||||
selectGesture.allowedPressTypes = [NSNumber(value: selectType.rawValue)]
|
||||
view.addGestureRecognizer(selectGesture)
|
||||
|
||||
|
||||
let backTapGesture = UITapGestureRecognizer(target: self, action: #selector(self.backButtonPressed(tap:)))
|
||||
let backPress = UIPress.PressType.menu
|
||||
backTapGesture.allowedPressTypes = [NSNumber(value: backPress.rawValue)]
|
||||
view.addGestureRecognizer(backTapGesture)
|
||||
|
||||
|
||||
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.userPanned(panGestureRecognizer:)))
|
||||
view.addGestureRecognizer(panGestureRecognizer)
|
||||
|
||||
let swipeRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(self.swipe(swipe:)))
|
||||
swipeRecognizer.direction = .right
|
||||
view.addGestureRecognizer(swipeRecognizer)
|
||||
|
||||
let swipeRecognizerl = UISwipeGestureRecognizer(target: self, action: #selector(self.swipe(swipe:)))
|
||||
swipeRecognizerl.direction = .left
|
||||
view.addGestureRecognizer(swipeRecognizerl)
|
||||
|
||||
}
|
||||
|
||||
@objc func backButtonPressed(tap: UITapGestureRecognizer) {
|
||||
print("back")
|
||||
// Dismiss info panel
|
||||
if showingInfoPanel {
|
||||
if focusedOnTabBar {
|
||||
@ -490,6 +473,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
seeking = false
|
||||
} else {
|
||||
// Dismiss view
|
||||
self.resignFirstResponder()
|
||||
mediaPlayer.stop()
|
||||
sendStopReport()
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
@ -497,16 +481,17 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
}
|
||||
|
||||
@objc func userPanned(panGestureRecognizer: UIPanGestureRecognizer) {
|
||||
print("pan")
|
||||
if loading {
|
||||
return
|
||||
}
|
||||
|
||||
let translation = panGestureRecognizer.translation(in: view)
|
||||
let velocity = panGestureRecognizer.velocity(in: view)
|
||||
|
||||
print(translation)
|
||||
|
||||
// Swiped up - Handle dismissing info panel
|
||||
if translation.y < -700 && (focusedOnTabBar && showingInfoPanel) {
|
||||
if translation.y < -200 && (focusedOnTabBar && showingInfoPanel) {
|
||||
toggleInfoContainer()
|
||||
return
|
||||
}
|
||||
@ -516,7 +501,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
}
|
||||
|
||||
// Swiped down - Show the info panel
|
||||
if translation.y > 700 {
|
||||
if translation.y > 200 {
|
||||
toggleInfoContainer()
|
||||
return
|
||||
}
|
||||
@ -546,32 +531,9 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
||||
|
||||
}
|
||||
|
||||
// Not currently used
|
||||
@objc func swipe(swipe: UISwipeGestureRecognizer!) {
|
||||
print("swiped")
|
||||
switch swipe.direction {
|
||||
case .left:
|
||||
print("swiped left")
|
||||
// mediaPlayer.pause()
|
||||
// player.seek(to: CMTime(value: Int64(self.currentSeconds) + 10, timescale: 1))
|
||||
// mediaPlayer.play()
|
||||
case .right:
|
||||
print("swiped right")
|
||||
// mediaPlayer.pause()
|
||||
// player.seek(to: CMTime(value: Int64(self.currentSeconds) + 10, timescale: 1))
|
||||
// mediaPlayer.play()
|
||||
case .up:
|
||||
break
|
||||
case .down:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Play/Pause or Select is pressed on the AppleTV remote
|
||||
@objc func selectButtonTapped() {
|
||||
print("select")
|
||||
if loading {
|
||||
return
|
||||
}
|
||||
|
@ -36,6 +36,8 @@
|
||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
|
||||
5321753E2671DE9C005491E6 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; };
|
||||
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */; };
|
||||
53272532268BF09D0035FBF1 /* MediaViewActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53272531268BF09D0035FBF1 /* MediaViewActionButton.swift */; };
|
||||
53272535268BF9710035FBF1 /* SwiftUIFocusGuide in Frameworks */ = {isa = PBXBuildFile; productRef = 53272534268BF9710035FBF1 /* SwiftUIFocusGuide */; };
|
||||
532E68CF267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532E68CE267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift */; };
|
||||
53313B90265EEA6D00947AA3 /* VideoPlayer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */; };
|
||||
53352571265EA0A0006CCA86 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53352570265EA0A0006CCA86 /* Introspect */; };
|
||||
@ -232,6 +234,7 @@
|
||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; };
|
||||
531AC8BE26750DE20091C7EB /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
53272531268BF09D0035FBF1 /* MediaViewActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaViewActionButton.swift; sourceTree = "<group>"; };
|
||||
532E68CE267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerCastDeviceSelector.swift; sourceTree = "<group>"; };
|
||||
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = VideoPlayer.storyboard; sourceTree = "<group>"; };
|
||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
|
||||
@ -353,6 +356,7 @@
|
||||
53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */,
|
||||
535870912669D7A800D05A09 /* Introspect in Frameworks */,
|
||||
62CB3F482685BB3B003D0A6F /* Defaults in Frameworks */,
|
||||
53272535268BF9710035FBF1 /* SwiftUIFocusGuide in Frameworks */,
|
||||
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
|
||||
536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
|
||||
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
|
||||
@ -503,6 +507,7 @@
|
||||
536D3D80267BDFC60004248C /* PortraitItemElement.swift */,
|
||||
536D3D87267C17350004248C /* PublicUserButton.swift */,
|
||||
53116A18268B947A003024C9 /* PlainLinkButton.swift */,
|
||||
53272531268BF09D0035FBF1 /* MediaViewActionButton.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@ -703,6 +708,7 @@
|
||||
53ABFDEC26799D7700886593 /* ActivityIndicator */,
|
||||
536D3D83267BEA550004248C /* ParallaxView */,
|
||||
62CB3F472685BB3B003D0A6F /* Defaults */,
|
||||
53272534268BF9710035FBF1 /* SwiftUIFocusGuide */,
|
||||
);
|
||||
productName = "JellyfinPlayer tvOS";
|
||||
productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */;
|
||||
@ -804,6 +810,7 @@
|
||||
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */,
|
||||
53EC6E23267EB10F006DD26A /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
|
||||
62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */,
|
||||
53272533268BF9710035FBF1 /* XCRemoteSwiftPackageReference "SwiftUIFocusGuide" */,
|
||||
);
|
||||
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -983,6 +990,7 @@
|
||||
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
||||
62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
||||
535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */,
|
||||
53272532268BF09D0035FBF1 /* MediaViewActionButton.swift in Sources */,
|
||||
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
|
||||
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
|
||||
62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
|
||||
@ -1447,6 +1455,14 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
53272533268BF9710035FBF1 /* XCRemoteSwiftPackageReference "SwiftUIFocusGuide" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/rmnblm/SwiftUIFocusGuide";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.1.0;
|
||||
};
|
||||
};
|
||||
5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect";
|
||||
@ -1514,6 +1530,11 @@
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
53272534268BF9710035FBF1 /* SwiftUIFocusGuide */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 53272533268BF9710035FBF1 /* XCRemoteSwiftPackageReference "SwiftUIFocusGuide" */;
|
||||
productName = SwiftUIFocusGuide;
|
||||
};
|
||||
53352570265EA0A0006CCA86 /* Introspect */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
||||
|
@ -91,6 +91,15 @@
|
||||
"version": "0.1.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftUIFocusGuide",
|
||||
"repositoryURL": "https://github.com/rmnblm/SwiftUIFocusGuide",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "fb8eefaccb2954efedc19a5539241f370baa4a10",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftyJSON",
|
||||
"repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON",
|
||||
|
Loading…
Reference in New Issue
Block a user