Merge pull request #90 from jellyfin/create-pull-request/patch
[ci] SwiftLint
This commit is contained in:
commit
3a2328fbee
|
@ -151,8 +151,7 @@ struct ConnectToServerView: View {
|
|||
}
|
||||
.onAppear(perform: self.viewModel.discoverServers)
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,7 @@ import SwiftUI
|
|||
|
||||
class AudioViewController: UIViewController {
|
||||
|
||||
var height : CGFloat = 420
|
||||
|
||||
var height: CGFloat = 420
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
@ -21,8 +20,7 @@ class AudioViewController: UIViewController {
|
|||
|
||||
}
|
||||
|
||||
func prepareAudioView(audioTracks: [AudioTrack], selectedTrack: Int32, delegate: VideoPlayerSettingsDelegate)
|
||||
{
|
||||
func prepareAudioView(audioTracks: [AudioTrack], selectedTrack: Int32, delegate: VideoPlayerSettingsDelegate) {
|
||||
let contentView = UIHostingController(rootView: AudioView(selectedTrack: selectedTrack, audioTrackArray: audioTracks, delegate: delegate))
|
||||
self.view.addSubview(contentView.view)
|
||||
contentView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -37,24 +35,23 @@ class AudioViewController: UIViewController {
|
|||
|
||||
struct AudioView: View {
|
||||
|
||||
@State var selectedTrack : Int32 = -1
|
||||
@State var selectedTrack: Int32 = -1
|
||||
@State var audioTrackArray: [AudioTrack] = []
|
||||
|
||||
weak var delegate: VideoPlayerSettingsDelegate?
|
||||
|
||||
var body : some View {
|
||||
NavigationView {
|
||||
VStack() {
|
||||
VStack {
|
||||
List(audioTrackArray, id: \.id) { track in
|
||||
Button(action: {
|
||||
delegate?.selectNew(audioTrack: track.id)
|
||||
selectedTrack = track.id
|
||||
}, label: {
|
||||
HStack(spacing: 10){
|
||||
HStack(spacing: 10) {
|
||||
if track.id == selectedTrack {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Image(systemName: "checkmark")
|
||||
.hidden()
|
||||
}
|
||||
|
|
|
@ -12,13 +12,12 @@ import JellyfinAPI
|
|||
|
||||
class InfoTabBarViewController: UITabBarController, UIGestureRecognizerDelegate {
|
||||
|
||||
var videoPlayer : VideoPlayerViewController? = nil
|
||||
var subtitleViewController : SubtitlesViewController? = nil
|
||||
var audioViewController : AudioViewController? = nil
|
||||
var mediaInfoController : MediaInfoViewController? = nil
|
||||
var infoContainerPos : CGRect? = nil
|
||||
var tabBarHeight : CGFloat = 0
|
||||
|
||||
var videoPlayer: VideoPlayerViewController?
|
||||
var subtitleViewController: SubtitlesViewController?
|
||||
var audioViewController: AudioViewController?
|
||||
var mediaInfoController: MediaInfoViewController?
|
||||
var infoContainerPos: CGRect?
|
||||
var tabBarHeight: CGFloat = 0
|
||||
|
||||
// override func viewWillAppear(_ animated: Bool) {
|
||||
// tabBar.standardAppearance.backgroundColor = .clear
|
||||
|
@ -52,7 +51,7 @@ class InfoTabBarViewController: UITabBarController, UIGestureRecognizerDelegate
|
|||
|
||||
}
|
||||
|
||||
func setupInfoViews(mediaItem: BaseItemDto, subtitleTracks: [Subtitle], selectedSubtitleTrack : Int32, audioTracks: [AudioTrack], selectedAudioTrack: Int32, delegate: VideoPlayerSettingsDelegate) {
|
||||
func setupInfoViews(mediaItem: BaseItemDto, subtitleTracks: [Subtitle], selectedSubtitleTrack: Int32, audioTracks: [AudioTrack], selectedAudioTrack: Int32, delegate: VideoPlayerSettingsDelegate) {
|
||||
|
||||
mediaInfoController?.setMedia(item: mediaItem)
|
||||
|
||||
|
@ -65,8 +64,6 @@ class InfoTabBarViewController: UITabBarController, UIGestureRecognizerDelegate
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||
|
@ -83,7 +80,6 @@ class InfoTabBarViewController: UITabBarController, UIGestureRecognizerDelegate
|
|||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
break
|
||||
case "Info":
|
||||
|
@ -97,7 +93,7 @@ class InfoTabBarViewController: UITabBarController, UIGestureRecognizerDelegate
|
|||
}
|
||||
break
|
||||
case "Subtitles":
|
||||
if var height = subtitleViewController?.height{
|
||||
if var height = subtitleViewController?.height {
|
||||
height += tabBarHeight
|
||||
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseOut) { [self] in
|
||||
videoPlayer?.infoViewContainer.frame = CGRect(x: pos.minX, y: pos.minY, width: pos.width, height: height)
|
||||
|
@ -115,8 +111,6 @@ class InfoTabBarViewController: UITabBarController, UIGestureRecognizerDelegate
|
|||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
// // In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
|
|
|
@ -13,8 +13,7 @@ import JellyfinAPI
|
|||
class MediaInfoViewController: UIViewController {
|
||||
private var contentView: UIHostingController<MediaInfoView>!
|
||||
|
||||
var height : CGFloat = 0
|
||||
|
||||
var height: CGFloat = 0
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
@ -22,8 +21,7 @@ class MediaInfoViewController: UIViewController {
|
|||
tabBarItem.title = "Info"
|
||||
}
|
||||
|
||||
func setMedia(item: BaseItemDto)
|
||||
{
|
||||
func setMedia(item: BaseItemDto) {
|
||||
contentView = UIHostingController(rootView: MediaInfoView(item: item))
|
||||
self.view.addSubview(contentView.view)
|
||||
contentView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -38,7 +36,7 @@ class MediaInfoViewController: UIViewController {
|
|||
}
|
||||
|
||||
struct MediaInfoView: View {
|
||||
@State var item : BaseItemDto? = nil
|
||||
@State var item: BaseItemDto?
|
||||
|
||||
var body: some View {
|
||||
if let item = item {
|
||||
|
@ -58,9 +56,7 @@ struct MediaInfoView: View {
|
|||
|
||||
Text(item.name ?? "Episode")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
Text(item.name ?? "Movie")
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
@ -102,7 +98,6 @@ struct MediaInfoView: View {
|
|||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
|
@ -111,15 +106,13 @@ struct MediaInfoView: View {
|
|||
}
|
||||
.padding(.leading, 350)
|
||||
.padding(.trailing, 125)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
func formatDate(date : Date) -> String{
|
||||
func formatDate(date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "d MMM yyyy"
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ import SwiftUI
|
|||
|
||||
class SubtitlesViewController: UIViewController {
|
||||
|
||||
var height : CGFloat = 420
|
||||
|
||||
var height: CGFloat = 420
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
@ -21,8 +20,7 @@ class SubtitlesViewController: UIViewController {
|
|||
|
||||
}
|
||||
|
||||
func prepareSubtitleView(subtitleTracks: [Subtitle], selectedTrack: Int32, delegate: VideoPlayerSettingsDelegate)
|
||||
{
|
||||
func prepareSubtitleView(subtitleTracks: [Subtitle], selectedTrack: Int32, delegate: VideoPlayerSettingsDelegate) {
|
||||
let contentView = UIHostingController(rootView: SubtitleView(selectedTrack: selectedTrack, subtitleTrackArray: subtitleTracks, delegate: delegate))
|
||||
self.view.addSubview(contentView.view)
|
||||
contentView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -36,25 +34,23 @@ class SubtitlesViewController: UIViewController {
|
|||
|
||||
struct SubtitleView: View {
|
||||
|
||||
@State var selectedTrack : Int32 = -1
|
||||
@State var selectedTrack: Int32 = -1
|
||||
@State var subtitleTrackArray: [Subtitle] = []
|
||||
|
||||
weak var delegate: VideoPlayerSettingsDelegate?
|
||||
|
||||
|
||||
var body : some View {
|
||||
NavigationView {
|
||||
VStack() {
|
||||
VStack {
|
||||
List(subtitleTrackArray, id: \.id) { track in
|
||||
Button(action: {
|
||||
delegate?.selectNew(subtitleTrack: track.id)
|
||||
selectedTrack = track.id
|
||||
}, label: {
|
||||
HStack(spacing: 10){
|
||||
HStack(spacing: 10) {
|
||||
if track.id == selectedTrack {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Image(systemName: "checkmark")
|
||||
.hidden()
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ protocol VideoPlayerSettingsDelegate: AnyObject {
|
|||
func selectNew(subtitleTrack id: Int32)
|
||||
}
|
||||
|
||||
class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate, VLCMediaPlayerDelegate, VLCMediaDelegate, UIGestureRecognizerDelegate {
|
||||
|
||||
class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate, VLCMediaPlayerDelegate, VLCMediaDelegate, UIGestureRecognizerDelegate {
|
||||
|
||||
@IBOutlet weak var videoContentView: UIView!
|
||||
@IBOutlet weak var controlsView: UIView!
|
||||
|
@ -37,12 +36,12 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
@IBOutlet weak var infoViewContainer: UIView!
|
||||
|
||||
var infoPanelDisplayPoint : CGPoint = .zero
|
||||
var infoPanelHiddenPoint : CGPoint = .zero
|
||||
var infoPanelDisplayPoint: CGPoint = .zero
|
||||
var infoPanelHiddenPoint: CGPoint = .zero
|
||||
|
||||
var containerViewController: InfoTabBarViewController?
|
||||
var focusedOnTabBar : Bool = false
|
||||
var showingInfoPanel : Bool = false
|
||||
var focusedOnTabBar: Bool = false
|
||||
var showingInfoPanel: Bool = false
|
||||
|
||||
var mediaPlayer = VLCMediaPlayer()
|
||||
|
||||
|
@ -69,33 +68,28 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
var showingControls: Bool = false
|
||||
var loading: Bool = true
|
||||
|
||||
var initialSeekPos : CGFloat = 0
|
||||
var initialSeekPos: CGFloat = 0
|
||||
var videoPos: Double = 0
|
||||
var videoDuration: Double = 0
|
||||
var controlsAppearTime: Double = 0
|
||||
|
||||
|
||||
var manifest: BaseItemDto = BaseItemDto()
|
||||
var playbackItem = PlaybackItem()
|
||||
var playSessionId: String = ""
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
|
||||
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
|
||||
|
||||
super.didUpdateFocus(in: context, with: coordinator)
|
||||
|
||||
// Check if focused on the tab bar, allows for swipe up to dismiss the info panel
|
||||
if context.nextFocusedView!.description.contains("UITabBarButton")
|
||||
{
|
||||
if context.nextFocusedView!.description.contains("UITabBarButton") {
|
||||
// Set value after half a second so info panel is not dismissed instantly when swiping up from content
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.focusedOnTabBar = true
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
focusedOnTabBar = false
|
||||
}
|
||||
|
||||
|
@ -115,7 +109,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
}
|
||||
|
||||
// Black gradient behind transport bar
|
||||
let gradientLayer:CAGradientLayer = CAGradientLayer()
|
||||
let gradientLayer: CAGradientLayer = CAGradientLayer()
|
||||
gradientLayer.frame.size = self.gradientView.frame.size
|
||||
gradientLayer.colors = [UIColor.black.withAlphaComponent(0.6).cgColor, UIColor.black.withAlphaComponent(0).cgColor]
|
||||
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
|
||||
|
@ -179,7 +173,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
}
|
||||
|
||||
let item = PlaybackItem()
|
||||
let streamURL : URL
|
||||
let streamURL: URL
|
||||
|
||||
// Item is being transcoded by request of server
|
||||
if let transcodiungUrl = mediaSource.transcodingUrl {
|
||||
|
@ -187,8 +181,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
streamURL = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(transcodiungUrl)")!
|
||||
}
|
||||
// Item will be directly played by the client
|
||||
else
|
||||
{
|
||||
else {
|
||||
item.videoType = .directPlay
|
||||
streamURL = URL(string: "\(ServerEnvironment.current.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(SessionManager.current.deviceID)&api_key=\(SessionManager.current.accessToken)&Tag=\(mediaSource.eTag!)")!
|
||||
}
|
||||
|
@ -202,7 +195,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
for stream in mediaSource.mediaStreams! {
|
||||
|
||||
if stream.type == .subtitle {
|
||||
var deliveryUrl: URL? = nil
|
||||
var deliveryUrl: URL?
|
||||
|
||||
if stream.deliveryMethod == .external {
|
||||
deliveryUrl = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(stream.deliveryUrl!)")!
|
||||
|
@ -210,7 +203,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec ?? "webvtt", languageCode: stream.language ?? "")
|
||||
|
||||
if stream.isDefault == true{
|
||||
if stream.isDefault == true {
|
||||
selectedCaptionTrack = Int32(stream.index!)
|
||||
}
|
||||
|
||||
|
@ -235,7 +228,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
selectedAudioTrack = audioTrackArray.first!.id
|
||||
}
|
||||
|
||||
|
||||
self.sendPlayReport()
|
||||
playbackItem = item
|
||||
|
||||
|
@ -274,7 +266,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
})
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,7 +354,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
if let imageData = NSData(contentsOf: manifest.getPrimaryImage(maxWidth: 200)) {
|
||||
if let artworkImage = UIImage(data: imageData as Data) {
|
||||
let artwork = MPMediaItemArtwork.init(boundsSize: artworkImage.size, requestHandler: { (size) -> UIImage in
|
||||
let artwork = MPMediaItemArtwork.init(boundsSize: artworkImage.size, requestHandler: { (_) -> UIImage in
|
||||
return artworkImage
|
||||
})
|
||||
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
|
||||
|
@ -372,11 +363,10 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
||||
|
||||
|
||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||
}
|
||||
|
||||
func updateNowPlayingCenter(time : Double?, playing : Bool?) {
|
||||
func updateNowPlayingCenter(time: Double?, playing: Bool?) {
|
||||
|
||||
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
|
||||
|
||||
|
@ -391,8 +381,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Grabs a refference to the info panel view controller
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "infoView" {
|
||||
|
@ -405,7 +393,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
// MARK: Player functions
|
||||
// Animate the scrubber when playing state changes
|
||||
func animateScrubber() {
|
||||
let y : CGFloat = playing ? 0 : -20
|
||||
let y: CGFloat = playing ? 0 : -20
|
||||
let height: CGFloat = playing ? 10 : 30
|
||||
|
||||
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
|
||||
|
@ -413,7 +401,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
func pause() {
|
||||
playing = false
|
||||
mediaPlayer.pause()
|
||||
|
@ -424,7 +411,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
animateScrubber()
|
||||
|
||||
self.scrubLabel.frame = CGRect(x: self.scrubberView.frame.minX - self.scrubLabel.frame.width/2, y:self.scrubLabel.frame.minY, width: self.scrubLabel.frame.width, height: self.scrubLabel.frame.height)
|
||||
self.scrubLabel.frame = CGRect(x: self.scrubberView.frame.minX - self.scrubLabel.frame.width/2, y: self.scrubLabel.frame.minY, width: self.scrubLabel.frame.width, height: self.scrubLabel.frame.height)
|
||||
}
|
||||
|
||||
func play () {
|
||||
|
@ -438,7 +425,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
animateScrubber()
|
||||
}
|
||||
|
||||
|
||||
func toggleInfoContainer() {
|
||||
showingInfoPanel.toggle()
|
||||
|
||||
|
@ -456,7 +442,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseOut) { [self] in
|
||||
UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseOut) { [self] in
|
||||
infoViewContainer.center = showingInfoPanel ? infoPanelDisplayPoint : infoPanelHiddenPoint
|
||||
}
|
||||
|
||||
|
@ -467,23 +453,22 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
let playPauseGesture = UITapGestureRecognizer(target: self, action: #selector(self.selectButtonTapped))
|
||||
let playPauseType = UIPress.PressType.playPause
|
||||
playPauseGesture.allowedPressTypes = [NSNumber(value: playPauseType.rawValue)];
|
||||
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)];
|
||||
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)];
|
||||
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)
|
||||
|
@ -494,7 +479,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
}
|
||||
|
||||
@objc func backButtonPressed(tap : UITapGestureRecognizer) {
|
||||
@objc func backButtonPressed(tap: UITapGestureRecognizer) {
|
||||
|
||||
// Dismiss info panel
|
||||
if showingInfoPanel {
|
||||
|
@ -505,16 +490,14 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
}
|
||||
|
||||
// Cancel seek and move back to initial position
|
||||
if(seeking) {
|
||||
if seeking {
|
||||
scrubLabel.isHidden = true
|
||||
UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseOut, animations: {
|
||||
self.scrubberView.frame = CGRect(x: self.initialSeekPos, y: 0, width: 2, height: 10)
|
||||
})
|
||||
play()
|
||||
seeking = false
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Dismiss view
|
||||
mediaPlayer.stop()
|
||||
sendStopReport()
|
||||
|
@ -522,7 +505,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
}
|
||||
}
|
||||
|
||||
@objc func userPanned(panGestureRecognizer : UIPanGestureRecognizer) {
|
||||
@objc func userPanned(panGestureRecognizer: UIPanGestureRecognizer) {
|
||||
if loading {
|
||||
return
|
||||
}
|
||||
|
@ -552,7 +535,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
}
|
||||
|
||||
// Save current position if seek is cancelled and show the scrubLabel
|
||||
if(!seeking) {
|
||||
if !seeking {
|
||||
initialSeekPos = self.scrubberView.frame.minX
|
||||
seeking = true
|
||||
self.scrubLabel.isHidden = false
|
||||
|
@ -569,7 +552,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Not currently used
|
||||
|
@ -606,9 +588,8 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
controlsView.isHidden = false
|
||||
controlsAppearTime = CACurrentMediaTime()
|
||||
|
||||
|
||||
// Move to seeked position
|
||||
if(seeking) {
|
||||
if seeking {
|
||||
scrubLabel.isHidden = true
|
||||
|
||||
// Move current time to the scrubbed position
|
||||
|
@ -634,7 +615,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
playing ? pause() : play()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Jellyfin Playstate updates
|
||||
func sendProgressReport(eventName: String) {
|
||||
if (eventName == "timeupdate" && mediaPlayer.state == .playing) || eventName != "timeupdate" {
|
||||
|
@ -678,7 +658,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
|
||||
// MARK: VLC Delegate
|
||||
|
||||
func mediaPlayerStateChanged(_ aNotification: Notification!) {
|
||||
|
@ -781,7 +760,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Settings Delegate
|
||||
func selectNew(audioTrack id: Int32) {
|
||||
selectedAudioTrack = id
|
||||
|
@ -797,7 +775,6 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
containerViewController?.setupInfoViews(mediaItem: manifest, subtitleTracks: subtitleTrackArray, selectedSubtitleTrack: selectedCaptionTrack, audioTracks: audioTrackArray, selectedAudioTrack: selectedAudioTrack, delegate: self)
|
||||
}
|
||||
|
||||
|
||||
func formatSecondsToHMS(_ seconds: Double) -> String {
|
||||
let timeHMSFormatter: DateComponentsFormatter = {
|
||||
let formatter = DateComponentsFormatter()
|
||||
|
|
|
@ -56,7 +56,7 @@ struct ContinueWatchingView: View {
|
|||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
if(item.type == "Episode") {
|
||||
if item.type == "Episode" {
|
||||
Text("• S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0)) - \(item.name ?? "")")
|
||||
.font(.callout)
|
||||
.fontWeight(.semibold)
|
||||
|
|
|
@ -16,7 +16,7 @@ struct HomeView: View {
|
|||
|
||||
@ViewBuilder
|
||||
var innerBody: some View {
|
||||
if(viewModel.isLoading) {
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
} else {
|
||||
ScrollView {
|
||||
|
|
|
@ -23,15 +23,14 @@ struct LatestMediaView: View {
|
|||
.shadow(radius: 4)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if(item.userData!.played ?? false) {
|
||||
if item.userData!.played ?? false {
|
||||
Image(systemName: "circle.fill")
|
||||
.foregroundColor(.white)
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
}.padding(2)
|
||||
.opacity(1)
|
||||
, alignment: .topTrailing).opacity(1)
|
||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
||||
Text(item.seriesName ?? item.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
|
|
|
@ -13,12 +13,12 @@ struct LibraryListView: View {
|
|||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack() {
|
||||
LazyVStack {
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(viewModel: .init(filters: viewModel.withFavorites), title: "Favorites")
|
||||
}) {
|
||||
ZStack() {
|
||||
HStack() {
|
||||
ZStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Your Favorites")
|
||||
.foregroundColor(.black)
|
||||
|
@ -38,8 +38,8 @@ struct LibraryListView: View {
|
|||
NavigationLink(destination: LazyView {
|
||||
Text("WIP")
|
||||
}) {
|
||||
ZStack() {
|
||||
HStack() {
|
||||
ZStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("All Genres")
|
||||
.foregroundColor(.black)
|
||||
|
@ -57,14 +57,14 @@ struct LibraryListView: View {
|
|||
.padding(.bottom, 15)
|
||||
|
||||
ForEach(viewModel.libraries, id: \.id) { library in
|
||||
if(library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows") {
|
||||
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "")
|
||||
}) {
|
||||
ZStack() {
|
||||
ZStack {
|
||||
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
||||
.opacity(0.4)
|
||||
HStack() {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(library.name ?? "")
|
||||
.foregroundColor(.white)
|
||||
|
|
|
@ -24,7 +24,7 @@ struct LibrarySearchView: View {
|
|||
Spacer().frame(height: 6)
|
||||
SearchBar(text: $searchQuery)
|
||||
ZStack {
|
||||
if(!viewModel.isLoading) {
|
||||
if !viewModel.isLoading {
|
||||
ScrollView(.vertical) {
|
||||
if !viewModel.items.isEmpty {
|
||||
Spacer().frame(height: 16)
|
||||
|
@ -37,15 +37,14 @@ struct LibrarySearchView: View {
|
|||
.cornerRadius(10)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if(item.userData!.played ?? false) {
|
||||
if item.userData!.played ?? false {
|
||||
Image(systemName: "circle.fill")
|
||||
.foregroundColor(.white)
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
}.padding(2)
|
||||
.opacity(1)
|
||||
, alignment: .topTrailing).opacity(1)
|
||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
||||
Text(item.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
|
|
|
@ -41,15 +41,14 @@ struct LibraryView: View {
|
|||
.cornerRadius(10)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if(item.userData!.played ?? false) {
|
||||
if item.userData!.played ?? false {
|
||||
Image(systemName: "circle.fill")
|
||||
.foregroundColor(.white)
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
}.padding(2)
|
||||
.opacity(1)
|
||||
, alignment: .topTrailing).opacity(1)
|
||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
||||
Text(item.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
|
|
|
@ -78,15 +78,14 @@ struct SeasonItemView: View {
|
|||
)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if(episode.userData!.played ?? false) {
|
||||
if episode.userData!.played ?? false {
|
||||
Image(systemName: "circle.fill")
|
||||
.foregroundColor(.white)
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
}.padding(2)
|
||||
.opacity(1)
|
||||
, alignment: .topTrailing).opacity(1)
|
||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("S\(String(episode.parentIndexNumber ?? 0)):E\(String(episode.indexNumber ?? 0))").font(.subheadline)
|
||||
|
|
|
@ -48,7 +48,7 @@ struct SettingsView: View {
|
|||
SearchablePicker(label: "Preferred subtitle language",
|
||||
options: viewModel.langs,
|
||||
optionToString: { $0.name },
|
||||
selected:Binding<TrackLanguage>(
|
||||
selected: Binding<TrackLanguage>(
|
||||
get: { viewModel.langs.first(where: { $0.isoCode == autoSelectSubtitlesLangcode }) ?? .auto },
|
||||
set: {autoSelectSubtitlesLangcode = $0.isoCode}
|
||||
)
|
||||
|
@ -71,7 +71,6 @@ struct SettingsView: View {
|
|||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignOut"), object: nil)
|
||||
|
||||
|
||||
SessionManager.current.logout()
|
||||
} label: {
|
||||
Text("Log out").font(.callout)
|
||||
|
|
|
@ -54,9 +54,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
var controlsAppearTime: Double = 0
|
||||
var isSeeking: Bool = false
|
||||
|
||||
var playerDestination: PlayerDestination = .local;
|
||||
var discoveredCastDevices: [GCKDevice] = [];
|
||||
var selectedCastDevice: GCKDevice?;
|
||||
var playerDestination: PlayerDestination = .local
|
||||
var discoveredCastDevices: [GCKDevice] = []
|
||||
var selectedCastDevice: GCKDevice?
|
||||
var jellyfinCastChannel: GCKGenericChannel?
|
||||
var remotePositionTicks: Int = 0
|
||||
private var castDiscoveryManager: GCKDiscoveryManager {
|
||||
|
@ -65,7 +65,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
private var castSessionManager: GCKSessionManager {
|
||||
return GCKCastContext.sharedInstance().sessionManager
|
||||
}
|
||||
var hasSentRemoteSeek: Bool = false;
|
||||
var hasSentRemoteSeek: Bool = false
|
||||
|
||||
var selectedAudioTrack: Int32 = -1
|
||||
var selectedCaptionTrack: Int32 = -1
|
||||
|
@ -78,10 +78,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
var playbackItem = PlaybackItem()
|
||||
var remoteTimeUpdateTimer: Timer?
|
||||
|
||||
|
||||
// MARK: IBActions
|
||||
@IBAction func seekSliderStart(_ sender: Any) {
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
sendProgressReport(eventName: "pause")
|
||||
mediaPlayer.pause()
|
||||
} else {
|
||||
|
@ -112,7 +111,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration)
|
||||
let offset = secondsScrubbedTo - videoPosition
|
||||
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
if offset > 0 {
|
||||
mediaPlayer.jumpForward(Int32(offset))
|
||||
} else {
|
||||
|
@ -131,7 +130,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
sendStopReport()
|
||||
mediaPlayer.stop()
|
||||
|
||||
if(castSessionManager.hasConnectedCastSession()) {
|
||||
if castSessionManager.hasConnectedCastSession() {
|
||||
castSessionManager.endSessionAndStopCasting(true)
|
||||
}
|
||||
|
||||
|
@ -139,13 +138,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
|
||||
@IBAction func controlViewTapped(_ sender: Any) {
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
videoControlsView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func contentViewTapped(_ sender: Any) {
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
videoControlsView.isHidden = false
|
||||
controlsAppearTime = CACurrentMediaTime()
|
||||
}
|
||||
|
@ -153,7 +152,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
@IBAction func jumpBackTapped(_ sender: Any) {
|
||||
if paused == false {
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
mediaPlayer.jumpBackward(15)
|
||||
} else {
|
||||
self.sendJellyfinCommand(command: "Seek", options: ["position": (remotePositionTicks/10_000_000)-15])
|
||||
|
@ -163,7 +162,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
@IBAction func jumpForwardTapped(_ sender: Any) {
|
||||
if paused == false {
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
mediaPlayer.jumpForward(30)
|
||||
} else {
|
||||
self.sendJellyfinCommand(command: "Seek", options: ["position": (remotePositionTicks/10_000_000)+30])
|
||||
|
@ -174,7 +173,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
@IBOutlet weak var mainActionButton: UIButton!
|
||||
@IBAction func mainActionButtonPressed(_ sender: Any) {
|
||||
if paused {
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
mediaPlayer.play()
|
||||
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
paused = false
|
||||
|
@ -184,7 +183,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
paused = false
|
||||
}
|
||||
} else {
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
mediaPlayer.pause()
|
||||
mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
||||
paused = true
|
||||
|
@ -211,9 +210,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
|
||||
//MARK: Cast methods
|
||||
// MARK: Cast methods
|
||||
@IBAction func castButtonPressed(_ sender: Any) {
|
||||
if(selectedCastDevice == nil) {
|
||||
if selectedCastDevice == nil {
|
||||
castDeviceVC = VideoPlayerCastDeviceSelectorView()
|
||||
castDeviceVC?.delegate = self
|
||||
|
||||
|
@ -228,7 +227,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
} else {
|
||||
castSessionManager.endSessionAndStopCasting(true)
|
||||
selectedCastDevice = nil;
|
||||
selectedCastDevice = nil
|
||||
self.castButton.isEnabled = true
|
||||
self.castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||
playerDestination = .local
|
||||
|
@ -237,24 +236,24 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
func castPopoverDismissed() {
|
||||
castDeviceVC?.dismiss(animated: true, completion: nil)
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
self.mediaPlayer.play()
|
||||
}
|
||||
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
}
|
||||
|
||||
func castDeviceChanged() {
|
||||
if(selectedCastDevice != nil) {
|
||||
if selectedCastDevice != nil {
|
||||
playerDestination = .remote
|
||||
castSessionManager.add(self)
|
||||
castSessionManager.startSession(with: selectedCastDevice!)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: Cast End
|
||||
// MARK: Cast End
|
||||
func settingsPopoverDismissed() {
|
||||
optionsVC?.dismiss(animated: true, completion: nil)
|
||||
if(playerDestination == .local) {
|
||||
if playerDestination == .local {
|
||||
self.mediaPlayer.play()
|
||||
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
}
|
||||
|
@ -270,7 +269,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
// Add handler for Pause Command
|
||||
commandCenter.pauseCommand.addTarget { _ in
|
||||
if(self.playerDestination == .local) {
|
||||
if self.playerDestination == .local {
|
||||
self.mediaPlayer.pause()
|
||||
self.sendProgressReport(eventName: "pause")
|
||||
} else {
|
||||
|
@ -282,7 +281,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
// Add handler for Play command
|
||||
commandCenter.playCommand.addTarget { _ in
|
||||
if(self.playerDestination == .local) {
|
||||
if self.playerDestination == .local {
|
||||
self.mediaPlayer.play()
|
||||
self.sendProgressReport(eventName: "unpause")
|
||||
} else {
|
||||
|
@ -294,7 +293,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
// Add handler for FF command
|
||||
commandCenter.seekForwardCommand.addTarget { _ in
|
||||
if(self.playerDestination == .local) {
|
||||
if self.playerDestination == .local {
|
||||
self.mediaPlayer.jumpForward(30)
|
||||
self.sendProgressReport(eventName: "timeupdate")
|
||||
} else {
|
||||
|
@ -305,7 +304,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
// Add handler for RW command
|
||||
commandCenter.seekBackwardCommand.addTarget { _ in
|
||||
if(self.playerDestination == .local) {
|
||||
if self.playerDestination == .local {
|
||||
self.mediaPlayer.jumpBackward(15)
|
||||
self.sendProgressReport(eventName: "timeupdate")
|
||||
} else {
|
||||
|
@ -324,7 +323,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
let videoPosition = Double(self.mediaPlayer.time.intValue)
|
||||
let offset = targetSeconds - videoPosition
|
||||
|
||||
if(self.playerDestination == .local) {
|
||||
if self.playerDestination == .local {
|
||||
if offset > 0 {
|
||||
self.mediaPlayer.jumpForward(Int32(offset)/1000)
|
||||
} else {
|
||||
|
@ -357,7 +356,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
||||
}
|
||||
|
||||
if(!UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat) {
|
||||
if !UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat {
|
||||
let value = UIInterfaceOrientation.landscapeRight.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
UIViewController.attemptRotationToDeviceOrientation()
|
||||
|
@ -366,7 +365,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
|
||||
func mediaHasStartedPlaying() {
|
||||
castButton.isHidden = true;
|
||||
castButton.isHidden = true
|
||||
let discoveryCriteria = GCKDiscoveryCriteria(applicationID: "F007D354")
|
||||
let gckCastOptions = GCKCastOptions(discoveryCriteria: discoveryCriteria)
|
||||
GCKCastContext.setSharedInstanceWith(gckCastOptions)
|
||||
|
@ -376,9 +375,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
|
||||
func didUpdateDeviceList() {
|
||||
let totalDevices = castDiscoveryManager.deviceCount;
|
||||
let totalDevices = castDiscoveryManager.deviceCount
|
||||
discoveredCastDevices = []
|
||||
if(totalDevices > 0) {
|
||||
if totalDevices > 0 {
|
||||
for i in 0...totalDevices-1 {
|
||||
let device = castDiscoveryManager.device(at: i)
|
||||
discoveredCastDevices.append(device)
|
||||
|
@ -403,7 +402,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
super.viewWillDisappear(animated)
|
||||
}
|
||||
|
||||
//MARK: viewDidAppear
|
||||
// MARK: viewDidAppear
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
overrideUserInterfaceStyle = .dark
|
||||
|
@ -572,8 +571,8 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
sendPlayReport()
|
||||
|
||||
// 1 second = 10,000,000 ticks
|
||||
var startTicks: Int64 = 0;
|
||||
if(remotePositionTicks == 0) {
|
||||
var startTicks: Int64 = 0
|
||||
if remotePositionTicks == 0 {
|
||||
print("Using server-reported start time")
|
||||
startTicks = manifest.userData?.playbackPositionTicks ?? 0
|
||||
} else {
|
||||
|
@ -582,7 +581,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
|
||||
if startTicks != 0 {
|
||||
let videoPosition = Double(mediaPlayer.time.intValue / 1000);
|
||||
let videoPosition = Double(mediaPlayer.time.intValue / 1000)
|
||||
let secondsScrubbedTo = startTicks / 10_000_000
|
||||
let offset = secondsScrubbedTo - Int64(videoPosition)
|
||||
print("Seeking to position: \(secondsScrubbedTo)")
|
||||
|
@ -593,7 +592,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
|
||||
if(fetchCaptions) {
|
||||
if fetchCaptions {
|
||||
print("Fetching captions.")
|
||||
// Pause and load captions into memory.
|
||||
mediaPlayer.pause()
|
||||
|
@ -633,15 +632,15 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
|
||||
//MARK: - GCKGenericChannelDelegate
|
||||
// MARK: - GCKGenericChannelDelegate
|
||||
extension PlayerViewController: GCKGenericChannelDelegate {
|
||||
@objc func updateRemoteTime() {
|
||||
castButton.setImage(UIImage(named: "CastConnected"), for: .normal)
|
||||
if(!paused) {
|
||||
remotePositionTicks = remotePositionTicks + 2_000_000; //add 0.2 secs every timer evt.
|
||||
if !paused {
|
||||
remotePositionTicks = remotePositionTicks + 2_000_000; // add 0.2 secs every timer evt.
|
||||
}
|
||||
|
||||
if(isSeeking == false) {
|
||||
if isSeeking == false {
|
||||
let remainingTime = (manifest.runTimeTicks! - Int64(remotePositionTicks))/10_000_000
|
||||
let hours = remainingTime / 3600
|
||||
let minutes = (remainingTime % 3600) / 60
|
||||
|
@ -663,19 +662,19 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
if let data = message.data(using: .utf8) {
|
||||
if let json = try? JSON(data: data) {
|
||||
let messageType = json["type"].string ?? ""
|
||||
if(messageType == "playbackprogress") {
|
||||
if messageType == "playbackprogress" {
|
||||
dump(json)
|
||||
if(remotePositionTicks > 100) {
|
||||
if(hasSentRemoteSeek == false) {
|
||||
hasSentRemoteSeek = true;
|
||||
if remotePositionTicks > 100 {
|
||||
if hasSentRemoteSeek == false {
|
||||
hasSentRemoteSeek = true
|
||||
sendJellyfinCommand(command: "Seek", options: [
|
||||
"position": Int(Float(manifest.runTimeTicks! / 10_000_000) * mediaPlayer.position)
|
||||
])
|
||||
}
|
||||
}
|
||||
paused = json["data"]["PlayState"]["IsPaused"].boolValue
|
||||
self.remotePositionTicks = json["data"]["PlayState"]["PositionTicks"].int ?? 0;
|
||||
if(remoteTimeUpdateTimer == nil) {
|
||||
self.remotePositionTicks = json["data"]["PlayState"]["PositionTicks"].int ?? 0
|
||||
if remoteTimeUpdateTimer == nil {
|
||||
remoteTimeUpdateTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(updateRemoteTime), userInfo: nil, repeats: true)
|
||||
}
|
||||
}
|
||||
|
@ -701,9 +700,9 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
|
||||
jellyfinCastChannel?.sendTextMessage(jsonData.rawString()!, error: nil)
|
||||
|
||||
if(command == "Seek") {
|
||||
if command == "Seek" {
|
||||
remotePositionTicks = remotePositionTicks + ((options["position"] as! Int) * 10_000_000)
|
||||
//Send playback report as Jellyfin Chromecast isn't smarter than a rock.
|
||||
// Send playback report as Jellyfin Chromecast isn't smarter than a rock.
|
||||
let progressInfo = PlaybackProgressInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: paused, isMuted: false, positionTicks: Int64(remotePositionTicks), playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
||||
|
||||
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: progressInfo)
|
||||
|
@ -717,15 +716,15 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
//MARK: - GCKSessionManagerListener
|
||||
// MARK: - GCKSessionManagerListener
|
||||
extension PlayerViewController: GCKSessionManagerListener {
|
||||
func sessionDidStart(manager: GCKSessionManager, didStart session: GCKCastSession) {
|
||||
self.sendStopReport()
|
||||
mediaPlayer.stop()
|
||||
|
||||
playerDestination = .remote
|
||||
videoContentView.isHidden = true;
|
||||
videoControlsView.isHidden = false;
|
||||
videoContentView.isHidden = true
|
||||
videoControlsView.isHidden = false
|
||||
castButton.setImage(UIImage(named: "CastConnected"), for: .normal)
|
||||
manager.currentCastSession?.start()
|
||||
|
||||
|
@ -765,11 +764,10 @@ extension PlayerViewController: GCKSessionManagerListener {
|
|||
dump(error)
|
||||
}
|
||||
|
||||
|
||||
func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKCastSession, withError error: Error?) {
|
||||
print("didEnd")
|
||||
playerDestination = .local;
|
||||
videoContentView.isHidden = false;
|
||||
playerDestination = .local
|
||||
videoContentView.isHidden = false
|
||||
remoteTimeUpdateTimer?.invalidate()
|
||||
castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||
startLocalPlaybackEngine(false)
|
||||
|
@ -777,15 +775,15 @@ extension PlayerViewController: GCKSessionManagerListener {
|
|||
|
||||
func sessionManager(_ sessionManager: GCKSessionManager, didSuspend session: GCKCastSession, with reason: GCKConnectionSuspendReason) {
|
||||
print("didSuspend")
|
||||
playerDestination = .local;
|
||||
videoContentView.isHidden = false;
|
||||
playerDestination = .local
|
||||
videoContentView.isHidden = false
|
||||
remoteTimeUpdateTimer?.invalidate()
|
||||
castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||
startLocalPlaybackEngine(false)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - VLCMediaPlayer Delegates
|
||||
// MARK: - VLCMediaPlayer Delegates
|
||||
extension PlayerViewController: VLCMediaPlayerDelegate {
|
||||
func mediaPlayerStateChanged(_ aNotification: Notification!) {
|
||||
let currentState: VLCMediaPlayerState = mediaPlayer.state
|
||||
|
@ -851,18 +849,7 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//MARK: End VideoPlayerVC
|
||||
// MARK: End VideoPlayerVC
|
||||
struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
||||
var item: BaseItemDto
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
@ -909,7 +896,7 @@ struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
|||
}
|
||||
}
|
||||
|
||||
//MARK: - Play State Update Methods
|
||||
// MARK: - Play State Update Methods
|
||||
extension PlayerViewController {
|
||||
func sendProgressReport(eventName: String) {
|
||||
if (eventName == "timeupdate" && mediaPlayer.state == .playing) || eventName != "timeupdate" {
|
||||
|
|
|
@ -43,9 +43,9 @@ struct VideoPlayerCastDeviceSelector: View {
|
|||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
if(!delegate.discoveredCastDevices.isEmpty) {
|
||||
if !delegate.discoveredCastDevices.isEmpty {
|
||||
List(delegate.discoveredCastDevices, id: \.deviceID) { device in
|
||||
HStack() {
|
||||
HStack {
|
||||
Text(device.friendlyName!)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
|
@ -55,7 +55,7 @@ struct VideoPlayerCastDeviceSelector: View {
|
|||
delegate?.castDeviceChanged()
|
||||
delegate?.castPopoverDismissed()
|
||||
} label: {
|
||||
HStack() {
|
||||
HStack {
|
||||
Text("Connect")
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
|
@ -91,4 +91,3 @@ struct VideoPlayerCastDeviceSelector: View {
|
|||
}.offset(y: UIDevice.current.userInterfaceIdiom == .pad ? 14 : 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import Darwin
|
|||
let INADDR_ANY = in_addr(s_addr: 0)
|
||||
let INADDR_BROADCAST = in_addr(s_addr: 0xffffffff)
|
||||
|
||||
|
||||
/// An object representing the UDP broadcast connection. Uses a dispatch source to handle the incoming traffic on the UDP socket.
|
||||
open class UDPBroadcastConnection {
|
||||
|
||||
|
@ -58,11 +57,11 @@ open class UDPBroadcastConnection {
|
|||
/// - Throws: Throws a `ConnectionError` if an error occurs.
|
||||
public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws {
|
||||
self.address = sockaddr_in(
|
||||
sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
|
||||
sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
|
||||
sin_family: sa_family_t(AF_INET),
|
||||
sin_port: UDPBroadcastConnection.htonsPort(port: port),
|
||||
sin_addr: INADDR_BROADCAST,
|
||||
sin_zero: ( 0, 0, 0, 0, 0, 0, 0, 0 )
|
||||
sin_port: UDPBroadcastConnection.htonsPort(port: port),
|
||||
sin_addr: INADDR_BROADCAST,
|
||||
sin_zero: ( 0, 0, 0, 0, 0, 0, 0, 0 )
|
||||
)
|
||||
|
||||
self.handler = handler
|
||||
|
@ -81,7 +80,6 @@ open class UDPBroadcastConnection {
|
|||
|
||||
// MARK: Interface
|
||||
|
||||
|
||||
/// Create a UDP socket for broadcasting and set up cancel and event handlers
|
||||
///
|
||||
/// - Throws: Throws a `ConnectionError` if an error occurs.
|
||||
|
@ -92,8 +90,8 @@ open class UDPBroadcastConnection {
|
|||
guard newSocket > 0 else { throw ConnectionError.createSocketFailed }
|
||||
|
||||
// Enable broadcast on socket
|
||||
var broadcastEnable = Int32(1);
|
||||
let ret = setsockopt(newSocket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout<UInt32>.size));
|
||||
var broadcastEnable = Int32(1)
|
||||
let ret = setsockopt(newSocket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout<UInt32>.size))
|
||||
if ret == -1 {
|
||||
debugPrint("Couldn't enable broadcast on socket")
|
||||
close(newSocket)
|
||||
|
@ -123,7 +121,7 @@ open class UDPBroadcastConnection {
|
|||
|
||||
// Set up cancel handler
|
||||
newResponseSource.setCancelHandler {
|
||||
//debugPrint("Closing UDP socket")
|
||||
// debugPrint("Closing UDP socket")
|
||||
let UDPSocket = Int32(newResponseSource.handle)
|
||||
shutdown(UDPSocket, SHUT_RDWR)
|
||||
close(UDPSocket)
|
||||
|
@ -158,12 +156,12 @@ open class UDPBroadcastConnection {
|
|||
|
||||
guard let endpoint = withUnsafePointer(to: &socketAddress, { self.getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1)) })
|
||||
else {
|
||||
//debugPrint("Failed to get the address and port from the socket address received from recvfrom")
|
||||
// debugPrint("Failed to get the address and port from the socket address received from recvfrom")
|
||||
self.closeConnection()
|
||||
return
|
||||
}
|
||||
|
||||
//debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)")
|
||||
// debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)")
|
||||
|
||||
let responseBytes = Data(response[0..<bytesRead])
|
||||
|
||||
|
@ -213,7 +211,7 @@ open class UDPBroadcastConnection {
|
|||
|
||||
guard sent > 0 else {
|
||||
if let errorString = String(validatingUTF8: strerror(errno)) {
|
||||
//debugPrint("UDP connection failed to send data: \(errorString)")
|
||||
// debugPrint("UDP connection failed to send data: \(errorString)")
|
||||
}
|
||||
closeConnection()
|
||||
throw ConnectionError.sendingMessageFailed(code: errno)
|
||||
|
@ -221,7 +219,7 @@ open class UDPBroadcastConnection {
|
|||
|
||||
if sent == broadcastMessageLength {
|
||||
// Success
|
||||
//debugPrint("UDP connection sent \(broadcastMessageLength) bytes")
|
||||
// debugPrint("UDP connection sent \(broadcastMessageLength) bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -276,15 +274,14 @@ open class UDPBroadcastConnection {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Prevents crashes when blocking calls are pending and the app is paused (via Home button).
|
||||
///
|
||||
/// - Parameter socket: The socket for which the signal should be disabled.
|
||||
fileprivate func setNoSigPipe(socket: CInt) {
|
||||
var no_sig_pipe: Int32 = 1;
|
||||
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout<Int32>.size));
|
||||
var no_sig_pipe: Int32 = 1
|
||||
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout<Int32>.size))
|
||||
}
|
||||
|
||||
fileprivate class func htonsPort(port: in_port_t) -> in_port_t {
|
||||
|
@ -298,8 +295,6 @@ open class UDPBroadcastConnection {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Created by Gunter Hager on 25.03.19.
|
||||
// Copyright © 2019 Gunter Hager. All rights reserved.
|
||||
//
|
||||
|
|
|
@ -75,7 +75,7 @@ final class ConnectToServerViewModel: ViewModel {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func connectToServer(at url : URL) {
|
||||
func connectToServer(at url: URL) {
|
||||
ServerEnvironment.current.create(with: url.absoluteString)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { result in
|
||||
|
|
|
@ -29,7 +29,7 @@ struct NextUpWidgetProvider: TimelineProvider {
|
|||
let savedUser = SessionManager.current.user
|
||||
var tempCancellables = Set<AnyCancellable>()
|
||||
|
||||
if(server != nil && savedUser != nil) {
|
||||
if server != nil && savedUser != nil {
|
||||
JellyfinAPI.basePath = server!.baseURI ?? ""
|
||||
TvShowsAPI.getNextUp(userId: savedUser!.user_id, limit: 3,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||
|
@ -74,7 +74,7 @@ struct NextUpWidgetProvider: TimelineProvider {
|
|||
|
||||
var tempCancellables = Set<AnyCancellable>()
|
||||
|
||||
if(server != nil && savedUser != nil) {
|
||||
if server != nil && savedUser != nil {
|
||||
JellyfinAPI.basePath = server!.baseURI ?? ""
|
||||
TvShowsAPI.getNextUp(userId: savedUser!.user_id, limit: 3,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||
|
|
Loading…
Reference in New Issue