Fix SwiftUI view crashing - fix captions in XIB

This commit is contained in:
Aiden Vigue 2021-05-28 00:10:36 -04:00
parent 484bd445e6
commit 6bfd0c4fc6
No known key found for this signature in database
GPG Key ID: E7570472648F4544
11 changed files with 127 additions and 160 deletions

View File

@ -478,7 +478,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 29;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9R8RREG67J;
ENABLE_BITCODE = NO;
@ -504,7 +504,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 29;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9R8RREG67J;

View File

@ -32,6 +32,7 @@ struct ConnectToServerView: View {
@State private var isSignInErrored = false;
@State private var isConnected = false;
@State private var serverName = "";
@State private var usernameDisabled: Bool = false;
@State private var publicUsers: [publicUser] = [];
@State private var lastPublicUsers: [publicUser] = [];
@Binding var rootIsActive : Bool
@ -244,6 +245,7 @@ struct ConnectToServerView: View {
TextField("Username", text: $username)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(usernameDisabled)
SecureField("Password", text: $password)
.disableAutocorrection(true)
.autocapitalization(.none)
@ -285,6 +287,7 @@ struct ConnectToServerView: View {
Section() {
Button {
_publicUsers.wrappedValue = _lastPublicUsers.wrappedValue
_usernameDisabled.wrappedValue = false;
} label: {
HStack() {
HStack() {
@ -304,6 +307,7 @@ struct ConnectToServerView: View {
if(pubuser.hasPassword) {
_lastPublicUsers.wrappedValue = _publicUsers.wrappedValue
_username.wrappedValue = pubuser.username
_usernameDisabled.wrappedValue = true;
_publicUsers.wrappedValue = []
} else {
_publicUsers.wrappedValue = []
@ -339,7 +343,9 @@ struct ConnectToServerView: View {
Section() {
Button() {
_lastPublicUsers.wrappedValue = _publicUsers.wrappedValue;
_publicUsers.wrappedValue = []
_username.wrappedValue = ""
} label: {
HStack() {
Text("Other User").font(.subheadline).fontWeight(.semibold)

View File

@ -204,7 +204,7 @@ struct ContentView: View {
LatestMediaView(library: library_id)
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
}
Spacer().frame(height: 20)
Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
}
.navigationTitle("Home")
.toolbar {

View File

@ -172,6 +172,7 @@ struct ContinueWatchingView: View {
}.padding(.trailing, 5)
}
}
Spacer().frame(width: 2)
}.frame(height: 215)
} else {
EmptyView()

View File

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>26</string>
<string>29</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@ -24,7 +24,7 @@ struct ItemView: View {
var body: some View {
if(playback.shouldPlay) {
LoadingView(isShowing: $shouldShowLoadingView) {
LoadingViewNoBlur(isShowing: $shouldShowLoadingView) {
VLCPlayerWithControls(item: playback.itemToPlay, loadBinding: $shouldShowLoadingView, pBinding: _playback.projectedValue.shouldPlay)
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)

View File

@ -2,10 +2,10 @@ import SwiftUI
struct LoadingView<Content>: View where Content: View {
@Environment(\.colorScheme) var colorScheme
@Binding var isShowing: Bool // should the modal be visible?
@Binding var isShowing: Bool // should the modal be visible?
var content: () -> Content
var text: String? // the text to display under the ProgressView - defaults to "Loading..."
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
@ -38,3 +38,42 @@ struct LoadingView<Content>: View where Content: View {
}
}
}
struct LoadingViewNoBlur<Content>: View where Content: View {
@Environment(\.colorScheme) var colorScheme
@Binding var isShowing: Bool // should the modal be visible?
var content: () -> Content
var text: String? // the text to display under the ProgressView - defaults to "Loading..."
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
// the content to display - if the modal is showing, we'll blur it
content()
.disabled(isShowing)
// all contents inside here will only be shown when isShowing is true
if isShowing {
// this Rectangle is a semi-transparent black overlay
Rectangle()
.fill(Color.black).opacity(isShowing ? 0.6 : 0)
.edgesIgnoringSafeArea(.all)
// the magic bit - our ProgressView just displays an activity
// indicator, with some text underneath showing what we are doing
HStack() {
ProgressView()
Text(text ?? "Loading").fontWeight(.semibold).font(.callout).offset(x: 60)
Spacer()
}
.padding(EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 10))
.frame(width: 250)
.background(colorScheme == .dark ? Color(UIColor.systemGray6) : Color.white)
.foregroundColor(Color.primary)
.cornerRadius(16)
}
}
}
}
}

View File

@ -44,7 +44,7 @@
<constraint firstAttribute="width" constant="91" id="LbL-h0-EYA"/>
<constraint firstAttribute="height" constant="34" id="OkD-Dr-Ina"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
@ -125,7 +125,7 @@
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" staticText="YES" header="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="highlightedColor" systemColor="labelColor"/>
</label>

View File

@ -39,7 +39,7 @@ protocol PlayerViewControllerDelegate: AnyObject {
func exitPlayer(_ viewController: PlayerViewController)
}
class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDelegate, VideoPlayerSettingsDelegate {
class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDelegate {
weak var delegate: PlayerViewControllerDelegate?
@ -58,14 +58,23 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
var shouldShowLoadingScreen: Bool = false;
var ssTargetValueOffset: Int = 0;
var ssStartValue: Int = 0;
var optionsVC: VideoPlayerSettingsView?;
var paused: Bool = true;
var lastTime: Float = 0.0;
var startTime: Int = 0;
var controlsAppearTime: Double = 0;
var selectedAudioTrack: Int32 = -1;
var selectedCaptionTrack: Int32 = -1;
var selectedAudioTrack: Int32 = -1 {
didSet {
print(selectedAudioTrack)
}
};
var selectedCaptionTrack: Int32 = -1 {
didSet {
print(selectedCaptionTrack)
}
}
var playSessionId: String = "";
var lastProgressReportTime: Double = 0;
@ -155,23 +164,15 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
}
@IBAction func settingsButtonTapped(_ sender: UIButton) {
let optionsVC = VideoPlayerSettingsView()
print(self.selectedAudioTrack)
print(self.selectedCaptionTrack)
optionsVC.currentSubtitleTrack = self.selectedCaptionTrack
optionsVC.currentAudioTrack = self.selectedAudioTrack
optionsVC.delegate = self;
optionsVC.subtitles = subtitleTrackArray
optionsVC.audioTracks = audioTrackArray
// Use the popover presentation style for your view controller.
let navVC = UINavigationController(rootViewController: optionsVC)
navVC.modalPresentationStyle = .popover
navVC.popoverPresentationController?.sourceView = playerSettingsButton
optionsVC = VideoPlayerSettingsView()
optionsVC?.delegate = self
optionsVC?.modalPresentationStyle = .popover
optionsVC?.popoverPresentationController?.sourceView = playerSettingsButton
// Present the view controller (in a popover).
self.present(navVC, animated: true) {
self.present(optionsVC!, animated: true) {
print("popover visible, pause playback")
self.mediaPlayer.pause()
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
@ -179,6 +180,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
}
func settingsPopoverDismissed() {
optionsVC?.dismiss(animated: true, completion: nil)
self.mediaPlayer.play()
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
}
@ -188,8 +190,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
//View has loaded.
//Show loading screen
delegate?.showLoadingView(self)
mediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
//mediaPlayer.wrappedValue.perform(Selector(("setTextRendererFont:")), with: "Copperplate")
@ -197,11 +198,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
mediaPlayer.delegate = self
mediaPlayer.drawable = videoContentView
if(manifest.Type == "Episode") {
titleLabel.text = "\(manifest.Name) - S\(String(manifest.ParentIndexNumber ?? 0)):E\(String(manifest.IndexNumber ?? 0)) - \(manifest.SeriesName ?? "")"
} else {
titleLabel.text = manifest.Name
}
titleLabel.text = manifest.Name
//Fetch max bitrate from UserDefaults depending on current connection mode
let defaults = UserDefaults.standard
@ -333,10 +330,12 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
//MARK: VideoPlayerSettings Delegate
func subtitleTrackChanged(newTrackID: Int32) {
selectedCaptionTrack = newTrackID
mediaPlayer.currentVideoSubTitleIndex = newTrackID
}
func audioTrackChanged(newTrackID: Int32) {
selectedAudioTrack = newTrackID
mediaPlayer.currentAudioTrackIndex = newTrackID
}
@ -400,7 +399,12 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
timeText.text = timeTextStr
if(CACurrentMediaTime() - controlsAppearTime > 5) {
videoControlsView.isHidden = true;
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
self.videoControlsView.alpha = 0.0
}, completion: { (finished: Bool) in
self.videoControlsView.isHidden = true;
self.videoControlsView.alpha = 1
})
controlsAppearTime = 10000000000000000000000;
}
} else {
@ -419,10 +423,6 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
var progressBody: String = "";
progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":\(mediaPlayer.state == .paused ? "true" : "false"),\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(mediaPlayer.position * Float(manifest.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[{\"start\":0,\"end\":569735888.888889}],\"PlayMethod\":\"\(playbackItem.videoType == .hls ? "Transcode" : "DirectStream")\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(manifest.Id)\",\"CanSeek\":true,\"ItemId\":\"\(manifest.Id)\",\"EventName\":\"\(eventName)\"}";
print("");
print("Sending progress report")
print(progressBody)
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Sessions/Playing/Progress")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
@ -445,10 +445,6 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":true,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(mediaPlayer.position * Float(manifest.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[{\"start\":0,\"end\":100000}],\"PlayMethod\":\"\(playbackItem.videoType == .hls ? "Transcode" : "DirectStream")\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(manifest.Id)\",\"CanSeek\":true,\"ItemId\":\"\(manifest.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(manifest.Id)\",\"PlaylistItemId\":\"playlistItem0\"}]}";
print("");
print("Sending stop report")
print(progressBody)
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Sessions/Playing/Stopped")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
@ -472,10 +468,6 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":false,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(manifest.Progress)),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[],\"PlayMethod\":\"\(playbackItem.videoType == .hls ? "Transcode" : "DirectStream")\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(manifest.Id)\",\"CanSeek\":true,\"ItemId\":\"\(manifest.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(manifest.Id)\",\"PlaylistItemId\":\"playlistItem0\"}]}";
print("");
print("Sending play report")
print(progressBody)
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Sessions/Playing")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"

View File

@ -6,139 +6,76 @@
//
import Foundation
import UIKit
import SwiftUI
import Combine
enum SettingsChangedEventTypes {
case subTrackChanged
case audioTrackChanged
}
struct settingsChangedEvent {
let eventType: SettingsChangedEventTypes
let payload: AnyObject
}
protocol VideoPlayerSettingsDelegate: AnyObject {
func subtitleTrackChanged(newTrackID: Int32)
func audioTrackChanged(newTrackID: Int32)
func settingsPopoverDismissed()
}
class SettingsViewDelegate: ObservableObject {
var subtitlesDidChange = PassthroughSubject<SettingsViewDelegate, Never>()
var subtitleTrackID: Int32 = 0 {
didSet {
self.subtitlesDidChange.send(self)
}
}
var audioTrackDidChange = PassthroughSubject<SettingsViewDelegate, Never>()
var audioTrackID: Int32 = 0 {
didSet {
self.audioTrackDidChange.send(self)
}
}
var shouldClose = PassthroughSubject<SettingsViewDelegate, Never>()
var close: Bool = false {
didSet {
self.shouldClose.send(self)
}
}
}
class VideoPlayerSettingsView: UIViewController {
private var ctntView: VideoPlayerSettings?
private var contentViewDelegate: SettingsViewDelegate = SettingsViewDelegate()
weak var delegate: VideoPlayerSettingsDelegate?
private var subChangePublisher: AnyCancellable?
private var audioChangePublisher: AnyCancellable?
private var shouldClosePublisher: AnyCancellable?
var subtitles: [Subtitle] = []
var audioTracks: [AudioTrack] = []
var currentSubtitleTrack: Int32?
var currentAudioTrack: Int32?
private var contentView: UIHostingController<VideoPlayerSettings>!
weak var delegate: PlayerViewController?
override func viewDidLoad() {
super.viewDidLoad()
ctntView = VideoPlayerSettings(delegate: self.contentViewDelegate, subtitles: self.subtitles, audioTracks: self.audioTracks, initSub: currentSubtitleTrack ?? -1, initAudio: currentAudioTrack ?? 1)
let contentView = UIHostingController(rootView: ctntView)
contentView = UIHostingController(rootView: VideoPlayerSettings(delegate: self.delegate ?? PlayerViewController()))
self.view.addSubview(contentView.view)
contentView.view.translatesAutoresizingMaskIntoConstraints = false
contentView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contentView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
self.subChangePublisher = self.contentViewDelegate.subtitlesDidChange.sink { suiDelegate in
self.delegate?.subtitleTrackChanged(newTrackID: suiDelegate.subtitleTrackID)
}
self.audioChangePublisher = self.contentViewDelegate.audioTrackDidChange.sink { suiDelegate in
self.delegate?.audioTrackChanged(newTrackID: suiDelegate.audioTrackID)
}
self.shouldClosePublisher = self.contentViewDelegate.shouldClose.sink { suiDelegate in
if(suiDelegate.close == true) {
self.delegate?.settingsPopoverDismissed()
self.dismiss(animated: true, completion: nil)
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.delegate?.settingsPopoverDismissed()
}
}
struct VideoPlayerSettings: View {
@ObservedObject var delegate: SettingsViewDelegate
@State private var subtitles: [Subtitle]
@State private var audioTracks: [AudioTrack]
@State private var subtitleSelection: Int32
@State private var audioTrackSelection: Int32
@State var delegate: PlayerViewController
@State var captionTrack: Int32 = -99;
@State var audioTrack: Int32 = -99;
init(delegate: SettingsViewDelegate, subtitles: [Subtitle], audioTracks: [AudioTrack], initSub: Int32, initAudio: Int32) {
init(delegate: PlayerViewController) {
self.delegate = delegate
self.subtitles = subtitles
self.audioTracks = audioTracks
subtitleSelection = initSub
audioTrackSelection = initAudio
}
var body: some View {
Form() {
if(UIDevice.current.userInterfaceIdiom == .phone) {
Button {
delegate.close = true
} label: {
HStack() {
Image(systemName: "chevron.left")
Text("Back").font(.callout)
NavigationView() {
Form() {
Picker("Closed Captions", selection: $captionTrack) {
ForEach(delegate.subtitleTrackArray, id: \.id) { caption in
Text(caption.name).tag(caption.id)
}
}
.onChange(of: captionTrack) { track in
self.delegate.subtitleTrackChanged(newTrackID: track)
}
Picker("Audio Track", selection: $audioTrack) {
ForEach(delegate.audioTrackArray, id: \.id) { caption in
Text(caption.name).tag(caption.id).lineLimit(1)
}
}.onChange(of: audioTrack) { track in
self.delegate.audioTrackChanged(newTrackID: track)
}
}.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Audio & Captions")
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
if(UIDevice.current.userInterfaceIdiom == .phone) {
Button {
self.delegate.settingsPopoverDismissed()
} label: {
HStack() {
Image(systemName: "chevron.left")
Text("Back").font(.callout)
}
}
}
}
}
Picker("Closed Captions", selection: $subtitleSelection) {
ForEach(subtitles, id: \.id) { caption in
Text(caption.name).tag(caption.id)
}
}.onChange(of: subtitleSelection) { id in
delegate.subtitleTrackID = id
}
Picker("Audio Track", selection: $audioTrackSelection) {
ForEach(audioTracks, id: \.id) { caption in
Text(caption.name).tag(caption.id).lineLimit(1)
}
}.onChange(of: audioTrackSelection) { id in
delegate.audioTrackID = id
}
}
}.offset(y: 14)
.onAppear(perform: {
_captionTrack.wrappedValue = self.delegate.selectedCaptionTrack
_audioTrack.wrappedValue = self.delegate.selectedAudioTrack
})
}
}

View File

@ -31,14 +31,6 @@ platform :ios do
project_slug: 'jellyfin-swift-ios',
dsym_path: "JellyfinPlayer.app.dSYM.zip"
)
set_github_release(
repository_name: "jellyfin/JellyfinPlayer",
name: "Release #{identifier_v}@#{identifier_s}",
tag_name: "v#{identifier_s}",
description: (File.read("Release Notes.rtf") rescue "No changelog provided"),
commitish: "main",
upload_assets: ["JellyfinPlayer.ipa"]
)
upload_to_testflight
dynatrace_process_symbols(
appId: "8c1f6941-ec78-480c-b589-b41aca29a52e",