lint autocorrect
This commit is contained in:
parent
f0107ae7ef
commit
61e6893cd6
|
@ -210,7 +210,7 @@ class EmailHelper: NSObject, MFMailComposeViewControllerDelegate {
|
|||
@main
|
||||
struct JellyfinPlayerApp: App {
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
|
||||
let persistenceController = PersistenceController.shared
|
||||
|
||||
var body: some Scene {
|
||||
|
@ -228,7 +228,7 @@ struct JellyfinPlayerApp: App {
|
|||
}
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
|
||||
|
||||
static var orientationLock = UIInterfaceOrientationMask.all
|
||||
|
||||
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
||||
|
|
|
@ -12,13 +12,13 @@ import SwiftUI
|
|||
struct LibrarySearchView: View {
|
||||
@StateObject var viewModel: LibrarySearchViewModel
|
||||
@State var searchQuery = ""
|
||||
|
||||
|
||||
@State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||
|
||||
|
||||
func recalcTracks() {
|
||||
tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack {
|
||||
|
@ -40,7 +40,7 @@ struct LibrarySearchView: View {
|
|||
}
|
||||
.navigationBarTitle("Search", displayMode: .inline)
|
||||
}
|
||||
|
||||
|
||||
var suggestionsListView: some View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 8) {
|
||||
|
@ -61,7 +61,7 @@ struct LibrarySearchView: View {
|
|||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var resultView: some View {
|
||||
let items = items(for: viewModel.selectedItemType)
|
||||
return VStack(alignment: .leading, spacing: 16) {
|
||||
|
@ -90,7 +90,7 @@ struct LibrarySearchView: View {
|
|||
recalcTracks()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func items(for type: ItemType) -> [BaseItemDto] {
|
||||
switch type {
|
||||
case .episode:
|
||||
|
@ -106,7 +106,7 @@ struct LibrarySearchView: View {
|
|||
}
|
||||
|
||||
private extension ItemType {
|
||||
|
||||
|
||||
var localized: String {
|
||||
switch self {
|
||||
case .episode:
|
||||
|
|
|
@ -26,12 +26,12 @@ protocol PlayerViewControllerDelegate: AnyObject {
|
|||
}
|
||||
|
||||
class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRemoteMediaClientListener {
|
||||
|
||||
|
||||
weak var delegate: PlayerViewControllerDelegate?
|
||||
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
var mediaPlayer = VLCMediaPlayer()
|
||||
|
||||
|
||||
@IBOutlet weak var upNextView: UIView!
|
||||
@IBOutlet weak var timeText: UILabel!
|
||||
@IBOutlet weak var videoContentView: UIView!
|
||||
|
@ -42,19 +42,19 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
@IBOutlet weak var jumpForwardButton: UIButton!
|
||||
@IBOutlet weak var playerSettingsButton: UIButton!
|
||||
@IBOutlet weak var castButton: UIButton!
|
||||
|
||||
|
||||
var shouldShowLoadingScreen: Bool = false
|
||||
var ssTargetValueOffset: Int = 0
|
||||
var ssStartValue: Int = 0
|
||||
var optionsVC: VideoPlayerSettingsView?
|
||||
var castDeviceVC: VideoPlayerCastDeviceSelectorView?
|
||||
|
||||
|
||||
var paused: Bool = true
|
||||
var lastTime: Float = 0.0
|
||||
var startTime: Int = 0
|
||||
var controlsAppearTime: Double = 0
|
||||
var isSeeking: Bool = false
|
||||
|
||||
|
||||
var playerDestination: PlayerDestination = .local
|
||||
var discoveredCastDevices: [GCKDevice] = []
|
||||
var selectedCastDevice: GCKDevice?
|
||||
|
@ -67,7 +67,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
return GCKCastContext.sharedInstance().sessionManager
|
||||
}
|
||||
var hasSentRemoteSeek: Bool = false
|
||||
|
||||
|
||||
var selectedPlaybackSpeedIndex: Int = 3
|
||||
var selectedAudioTrack: Int32 = -1
|
||||
var selectedCaptionTrack: Int32 = -1
|
||||
|
@ -76,13 +76,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
var subtitleTrackArray: [Subtitle] = []
|
||||
var audioTrackArray: [AudioTrack] = []
|
||||
let playbackSpeeds: [Float] = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
|
||||
|
||||
|
||||
var manifest: BaseItemDto = BaseItemDto()
|
||||
var playbackItem = PlaybackItem()
|
||||
var remoteTimeUpdateTimer: Timer?
|
||||
var upNextViewModel: UpNextViewModel = UpNextViewModel()
|
||||
var lastOri: UIInterfaceOrientation!
|
||||
|
||||
|
||||
// MARK: IBActions
|
||||
@IBAction func seekSliderStart(_ sender: Any) {
|
||||
if playerDestination == .local {
|
||||
|
@ -92,7 +92,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
isSeeking = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func seekSliderValueChanged(_ sender: Any) {
|
||||
let videoDuration: Double = Double(manifest.runTimeTicks! / Int64(10_000_000))
|
||||
let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration)
|
||||
|
@ -107,7 +107,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
timeText.text = "\(String(Int(floor(minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int(floor(seconds))).leftPad(toWidth: 2, withString: "0"))"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func seekSliderEnd(_ sender: Any) {
|
||||
isSeeking = false
|
||||
let videoPosition = playerDestination == .local ? Double(mediaPlayer.time.intValue / 1000) : Double(remotePositionTicks / Int(10_000_000))
|
||||
|
@ -115,7 +115,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
// Scrub is value from 0..1 - find position in video and add / or remove.
|
||||
let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration)
|
||||
let offset = secondsScrubbedTo - videoPosition
|
||||
|
||||
|
||||
if playerDestination == .local {
|
||||
if offset > 0 {
|
||||
mediaPlayer.jumpForward(Int32(offset))
|
||||
|
@ -130,18 +130,18 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func exitButtonPressed(_ sender: Any) {
|
||||
sendStopReport()
|
||||
mediaPlayer.stop()
|
||||
|
||||
|
||||
if castSessionManager.hasConnectedCastSession() {
|
||||
castSessionManager.endSessionAndStopCasting(true)
|
||||
}
|
||||
|
||||
|
||||
delegate?.exitPlayer(self)
|
||||
}
|
||||
|
||||
|
||||
@IBAction func controlViewTapped(_ sender: Any) {
|
||||
if playerDestination == .local {
|
||||
videoControlsView.isHidden = true
|
||||
|
@ -150,14 +150,14 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func contentViewTapped(_ sender: Any) {
|
||||
if playerDestination == .local {
|
||||
videoControlsView.isHidden = false
|
||||
controlsAppearTime = CACurrentMediaTime()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func jumpBackTapped(_ sender: Any) {
|
||||
if paused == false {
|
||||
if playerDestination == .local {
|
||||
|
@ -167,7 +167,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func jumpForwardTapped(_ sender: Any) {
|
||||
if paused == false {
|
||||
if playerDestination == .local {
|
||||
|
@ -177,7 +177,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBOutlet weak var mainActionButton: UIButton!
|
||||
@IBAction func mainActionButtonPressed(_ sender: Any) {
|
||||
if paused {
|
||||
|
@ -202,14 +202,14 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func settingsButtonTapped(_ sender: UIButton) {
|
||||
optionsVC = VideoPlayerSettingsView()
|
||||
optionsVC?.delegate = self
|
||||
|
||||
|
||||
optionsVC?.modalPresentationStyle = .popover
|
||||
optionsVC?.popoverPresentationController?.sourceView = playerSettingsButton
|
||||
|
||||
|
||||
// Present the view controller (in a popover).
|
||||
self.present(optionsVC!, animated: true) {
|
||||
print("popover visible, pause playback")
|
||||
|
@ -217,17 +217,17 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cast methods
|
||||
@IBAction func castButtonPressed(_ sender: Any) {
|
||||
if selectedCastDevice == nil {
|
||||
LogManager.shared.log.debug("Presenting Cast modal")
|
||||
castDeviceVC = VideoPlayerCastDeviceSelectorView()
|
||||
castDeviceVC?.delegate = self
|
||||
|
||||
|
||||
castDeviceVC?.modalPresentationStyle = .popover
|
||||
castDeviceVC?.popoverPresentationController?.sourceView = castButton
|
||||
|
||||
|
||||
// Present the view controller (in a popover).
|
||||
self.present(castDeviceVC!, animated: true) {
|
||||
self.mediaPlayer.pause()
|
||||
|
@ -242,7 +242,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
playerDestination = .local
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func castPopoverDismissed() {
|
||||
LogManager.shared.log.debug("Cast modal dismissed")
|
||||
castDeviceVC?.dismiss(animated: true, completion: nil)
|
||||
|
@ -251,7 +251,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
}
|
||||
|
||||
|
||||
func castDeviceChanged() {
|
||||
LogManager.shared.log.debug("Cast device changed")
|
||||
if selectedCastDevice != nil {
|
||||
|
@ -261,7 +261,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
castSessionManager.startSession(with: selectedCastDevice!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cast End
|
||||
func settingsPopoverDismissed() {
|
||||
optionsVC?.dismiss(animated: true, completion: nil)
|
||||
|
@ -270,7 +270,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func setupNowPlayingCC() {
|
||||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
commandCenter.playCommand.isEnabled = true
|
||||
|
@ -278,7 +278,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
commandCenter.seekForwardCommand.isEnabled = true
|
||||
commandCenter.seekBackwardCommand.isEnabled = true
|
||||
commandCenter.changePlaybackPositionCommand.isEnabled = true
|
||||
|
||||
|
||||
// Add handler for Pause Command
|
||||
commandCenter.pauseCommand.addTarget { _ in
|
||||
if self.playerDestination == .local {
|
||||
|
@ -290,7 +290,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
||||
return .success
|
||||
}
|
||||
|
||||
|
||||
// Add handler for Play command
|
||||
commandCenter.playCommand.addTarget { _ in
|
||||
if self.playerDestination == .local {
|
||||
|
@ -302,7 +302,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
return .success
|
||||
}
|
||||
|
||||
|
||||
// Add handler for FF command
|
||||
commandCenter.seekForwardCommand.addTarget { _ in
|
||||
if self.playerDestination == .local {
|
||||
|
@ -313,7 +313,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
|
||||
// Add handler for RW command
|
||||
commandCenter.seekBackwardCommand.addTarget { _ in
|
||||
if self.playerDestination == .local {
|
||||
|
@ -324,17 +324,17 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
|
||||
// Scrubber
|
||||
commandCenter.changePlaybackPositionCommand.addTarget { [weak self](remoteEvent) -> MPRemoteCommandHandlerStatus in
|
||||
guard let self = self else {return .commandFailed}
|
||||
|
||||
|
||||
if let event = remoteEvent as? MPChangePlaybackPositionCommandEvent {
|
||||
let targetSeconds = event.positionTime
|
||||
|
||||
|
||||
let videoPosition = Double(self.mediaPlayer.time.intValue)
|
||||
let offset = targetSeconds - videoPosition
|
||||
|
||||
|
||||
if self.playerDestination == .local {
|
||||
if offset > 0 {
|
||||
self.mediaPlayer.jumpForward(Int32(offset)/1000)
|
||||
|
@ -343,34 +343,34 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
self.sendProgressReport(eventName: "unpause")
|
||||
} else {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return .success
|
||||
} else {
|
||||
return .commandFailed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var nowPlayingInfo = [String: Any]()
|
||||
|
||||
|
||||
var runTicks = 0
|
||||
var playbackTicks = 0
|
||||
|
||||
|
||||
if let ticks = manifest.runTimeTicks {
|
||||
runTicks = Int(ticks / 10_000_000)
|
||||
}
|
||||
|
||||
|
||||
if let ticks = manifest.userData?.playbackPositionTicks {
|
||||
playbackTicks = Int(ticks / 10_000_000)
|
||||
}
|
||||
|
||||
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = manifest.name ?? "Jellyfin Video"
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = AVMediaType.video
|
||||
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = runTicks
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = playbackTicks
|
||||
|
||||
|
||||
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: { (_) -> UIImage in
|
||||
|
@ -379,12 +379,12 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
||||
|
||||
|
||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||
}
|
||||
|
||||
|
||||
// MARK: viewDidLoad
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
@ -392,28 +392,28 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
titleLabel.text = manifest.name ?? ""
|
||||
} else {
|
||||
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
||||
|
||||
|
||||
setupNextUpView()
|
||||
upNextViewModel.delegate = self
|
||||
}
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.lastOri = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
|
||||
AppDelegate.orientationLock = .landscape
|
||||
|
||||
|
||||
if !self.lastOri.isLandscape {
|
||||
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
|
||||
UIViewController.attemptRotationToDeviceOrientation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didChangedOrientation), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
|
||||
@objc func didChangedOrientation() {
|
||||
lastOri = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
|
||||
}
|
||||
|
||||
|
||||
func mediaHasStartedPlaying() {
|
||||
castButton.isHidden = true
|
||||
let discoveryCriteria = GCKDiscoveryCriteria(applicationID: "F007D354")
|
||||
|
@ -423,7 +423,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
castDiscoveryManager.add(self)
|
||||
castDiscoveryManager.startDiscovery()
|
||||
}
|
||||
|
||||
|
||||
func didUpdateDeviceList() {
|
||||
let totalDevices = castDiscoveryManager.deviceCount
|
||||
discoveredCastDevices = []
|
||||
|
@ -433,7 +433,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
discoveredCastDevices.append(device)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !discoveredCastDevices.isEmpty {
|
||||
castButton.isHidden = false
|
||||
castButton.isEnabled = true
|
||||
|
@ -444,7 +444,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
castButton.setImage(nil, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
self.tabBarController?.tabBar.isHidden = false
|
||||
|
@ -456,25 +456,25 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
UIViewController.attemptRotationToDeviceOrientation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: viewDidAppear
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
overrideUserInterfaceStyle = .dark
|
||||
self.tabBarController?.tabBar.isHidden = true
|
||||
self.navigationController?.isNavigationBarHidden = true
|
||||
|
||||
|
||||
mediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
|
||||
// mediaPlayer.wrappedValue.perform(Selector(("setTextRendererFont:")), with: "Copperplate")
|
||||
|
||||
|
||||
mediaPlayer.delegate = self
|
||||
mediaPlayer.drawable = videoContentView
|
||||
|
||||
|
||||
setupMediaPlayer()
|
||||
}
|
||||
|
||||
|
||||
func setupMediaPlayer() {
|
||||
|
||||
|
||||
// Fetch max bitrate from UserDefaults depending on current connection mode
|
||||
let maxBitrate = Defaults[.inNetworkBandwidth]
|
||||
print(maxBitrate)
|
||||
|
@ -484,7 +484,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
let profile = builder.buildProfile()
|
||||
dump(profile)
|
||||
let playbackInfo = PlaybackInfoDto(userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, autoOpenLiveStream: true)
|
||||
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [self] in
|
||||
delegate?.showLoadingView(self)
|
||||
MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo)
|
||||
|
@ -514,10 +514,10 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
let item = PlaybackItem()
|
||||
item.videoType = .transcode
|
||||
item.videoUrl = streamURL!
|
||||
|
||||
|
||||
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: nil, delivery: .embed, codec: "", languageCode: "")
|
||||
subtitleTrackArray.append(disableSubtitleTrack)
|
||||
|
||||
|
||||
// Loop through media streams and add to array
|
||||
for stream in mediaSource.mediaStreams ?? [] {
|
||||
if stream.type == .subtitle {
|
||||
|
@ -528,12 +528,12 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
deliveryUrl = nil
|
||||
}
|
||||
let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec ?? "webvtt", languageCode: stream.language ?? "")
|
||||
|
||||
|
||||
if subtitle.delivery != .encode {
|
||||
subtitleTrackArray.append(subtitle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if stream.type == .audio {
|
||||
let subtitle = AudioTrack(name: stream.displayTitle!, languageCode: stream.language ?? "", id: Int32(stream.index!))
|
||||
if stream.isDefault! == true {
|
||||
|
@ -542,26 +542,26 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
audioTrackArray.append(subtitle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if selectedAudioTrack == -1 {
|
||||
if audioTrackArray.count > 0 {
|
||||
selectedAudioTrack = audioTrackArray[0].id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.sendPlayReport()
|
||||
playbackItem = item
|
||||
} else {
|
||||
// Item will be directly played by the client.
|
||||
let streamURL: URL = 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 ?? "")")!
|
||||
|
||||
|
||||
let item = PlaybackItem()
|
||||
item.videoUrl = streamURL
|
||||
item.videoType = .directPlay
|
||||
|
||||
|
||||
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: nil, delivery: .embed, codec: "", languageCode: "")
|
||||
subtitleTrackArray.append(disableSubtitleTrack)
|
||||
|
||||
|
||||
// Loop through media streams and add to array
|
||||
for stream in mediaSource.mediaStreams ?? [] {
|
||||
if stream.type == .subtitle {
|
||||
|
@ -572,12 +572,12 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
deliveryUrl = nil
|
||||
}
|
||||
let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!, languageCode: stream.language ?? "")
|
||||
|
||||
|
||||
if subtitle.delivery != .encode {
|
||||
subtitleTrackArray.append(subtitle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if stream.type == .audio {
|
||||
let subtitle = AudioTrack(name: stream.displayTitle!, languageCode: stream.language ?? "", id: Int32(stream.index!))
|
||||
if stream.isDefault! == true {
|
||||
|
@ -586,25 +586,25 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
audioTrackArray.append(subtitle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if selectedAudioTrack == -1 {
|
||||
if audioTrackArray.count > 0 {
|
||||
selectedAudioTrack = audioTrackArray[0].id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.sendPlayReport()
|
||||
playbackItem = item
|
||||
|
||||
|
||||
// self.setupNowPlayingCC()
|
||||
}
|
||||
|
||||
|
||||
startLocalPlaybackEngine(true)
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func setupTracksForPreferredDefaults() {
|
||||
subtitleTrackArray.forEach { subtitle in
|
||||
if Defaults[.isAutoSelectSubtitles] {
|
||||
|
@ -618,7 +618,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
audioTrackArray.forEach { audio in
|
||||
if audio.languageCode.contains(Defaults[.autoSelectAudioLangCode]) {
|
||||
selectedAudioTrack = audio.id
|
||||
|
@ -626,12 +626,12 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func startLocalPlaybackEngine(_ fetchCaptions: Bool) {
|
||||
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
|
||||
mediaPlayer.play()
|
||||
sendPlayReport()
|
||||
|
||||
|
||||
// 1 second = 10,000,000 ticks
|
||||
var startTicks: Int64 = 0
|
||||
if remotePositionTicks == 0 {
|
||||
|
@ -639,7 +639,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
} else {
|
||||
startTicks = Int64(remotePositionTicks)
|
||||
}
|
||||
|
||||
|
||||
if startTicks != 0 {
|
||||
let videoPosition = Double(mediaPlayer.time.intValue / 1000)
|
||||
let secondsScrubbedTo = startTicks / 10_000_000
|
||||
|
@ -650,7 +650,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
mediaPlayer.jumpBackward(Int32(abs(offset)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if fetchCaptions {
|
||||
mediaPlayer.pause()
|
||||
subtitleTrackArray.forEach { sub in
|
||||
|
@ -659,47 +659,47 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.mediaHasStartedPlaying()
|
||||
delegate?.hideLoadingView(self)
|
||||
|
||||
|
||||
videoContentView.setNeedsLayout()
|
||||
videoContentView.setNeedsDisplay()
|
||||
self.view.setNeedsLayout()
|
||||
self.view.setNeedsDisplay()
|
||||
self.videoControlsView.setNeedsLayout()
|
||||
self.videoControlsView.setNeedsDisplay()
|
||||
|
||||
|
||||
mediaPlayer.pause()
|
||||
mediaPlayer.play()
|
||||
setupTracksForPreferredDefaults()
|
||||
}
|
||||
|
||||
|
||||
// MARK: VideoPlayerSettings Delegate
|
||||
func subtitleTrackChanged(newTrackID: Int32) {
|
||||
selectedCaptionTrack = newTrackID
|
||||
mediaPlayer.currentVideoSubTitleIndex = newTrackID
|
||||
}
|
||||
|
||||
|
||||
func audioTrackChanged(newTrackID: Int32) {
|
||||
selectedAudioTrack = newTrackID
|
||||
mediaPlayer.currentAudioTrackIndex = newTrackID
|
||||
}
|
||||
|
||||
|
||||
func playbackSpeedChanged(index: Int) {
|
||||
selectedPlaybackSpeedIndex = index
|
||||
mediaPlayer.rate = playbackSpeeds[index]
|
||||
}
|
||||
|
||||
|
||||
func smallNextUpView() {
|
||||
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn) { [self] in
|
||||
upNextViewModel.largeView = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func setupNextUpView() {
|
||||
getNextEpisode()
|
||||
|
||||
|
||||
// Create the swiftUI view
|
||||
let contentView = UIHostingController(rootView: VideoUpNextView(viewModel: upNextViewModel))
|
||||
self.upNextView.addSubview(contentView.view)
|
||||
|
@ -710,7 +710,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
contentView.view.leftAnchor.constraint(equalTo: upNextView.leftAnchor).isActive = true
|
||||
contentView.view.rightAnchor.constraint(equalTo: upNextView.rightAnchor).isActive = true
|
||||
}
|
||||
|
||||
|
||||
func getNextEpisode() {
|
||||
TvShowsAPI.getEpisodes(seriesId: manifest.seriesId!, userId: SessionManager.current.user.user_id!, startItemId: manifest.id, limit: 2)
|
||||
.sink(receiveCompletion: { completion in
|
||||
|
@ -724,21 +724,21 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
|
||||
func setPlayerToNextUp() {
|
||||
mediaPlayer.stop()
|
||||
|
||||
|
||||
ssTargetValueOffset = 0
|
||||
ssStartValue = 0
|
||||
|
||||
|
||||
paused = true
|
||||
lastTime = 0.0
|
||||
startTime = 0
|
||||
controlsAppearTime = 0
|
||||
isSeeking = false
|
||||
|
||||
|
||||
remotePositionTicks = 0
|
||||
|
||||
|
||||
selectedPlaybackSpeedIndex = 3
|
||||
selectedAudioTrack = -1
|
||||
selectedCaptionTrack = -1
|
||||
|
@ -746,22 +746,22 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
lastProgressReportTime = 0
|
||||
subtitleTrackArray = []
|
||||
audioTrackArray = []
|
||||
|
||||
|
||||
manifest = upNextViewModel.item!
|
||||
playbackItem = PlaybackItem()
|
||||
|
||||
|
||||
upNextViewModel.item = nil
|
||||
|
||||
|
||||
upNextView.isHidden = true
|
||||
shouldShowLoadingScreen = true
|
||||
videoControlsView.isHidden = true
|
||||
|
||||
|
||||
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
||||
|
||||
|
||||
setupMediaPlayer()
|
||||
getNextEpisode()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - GCKGenericChannelDelegate
|
||||
|
@ -771,7 +771,7 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
if !paused {
|
||||
remotePositionTicks = remotePositionTicks + 2_000_000; // add 0.2 secs every timer evt.
|
||||
}
|
||||
|
||||
|
||||
if isSeeking == false {
|
||||
let remainingTime = (manifest.runTimeTicks! - Int64(remotePositionTicks))/10_000_000
|
||||
let hours = remainingTime / 3600
|
||||
|
@ -784,12 +784,12 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
timeTextStr = "\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"
|
||||
}
|
||||
timeText.text = timeTextStr
|
||||
|
||||
|
||||
let playbackProgress = Float(remotePositionTicks) / Float(manifest.runTimeTicks!)
|
||||
seekSlider.setValue(playbackProgress, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func cast(_ channel: GCKGenericChannel, didReceiveTextMessage message: String, withNamespace protocolNamespace: String) {
|
||||
if let data = message.data(using: .utf8) {
|
||||
if let json = try? JSON(data: data) {
|
||||
|
@ -813,7 +813,7 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func sendJellyfinCommand(command: String, options: [String: Any]) {
|
||||
let payload: [String: Any] = [
|
||||
"options": options,
|
||||
|
@ -828,14 +828,14 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
"subtitleBurnIn": false
|
||||
]
|
||||
let jsonData = JSON(payload)
|
||||
|
||||
|
||||
jellyfinCastChannel?.sendTextMessage(jsonData.rawString()!, error: nil)
|
||||
|
||||
|
||||
if command == "Seek" {
|
||||
remotePositionTicks = remotePositionTicks + ((options["position"] as! Int) * 10_000_000)
|
||||
// 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)
|
||||
.sink(receiveCompletion: { result in
|
||||
print(result)
|
||||
|
@ -852,20 +852,20 @@ extension PlayerViewController: GCKSessionManagerListener {
|
|||
func sessionDidStart(manager: GCKSessionManager, didStart session: GCKCastSession) {
|
||||
self.sendStopReport()
|
||||
mediaPlayer.stop()
|
||||
|
||||
|
||||
playerDestination = .remote
|
||||
videoContentView.isHidden = true
|
||||
videoControlsView.isHidden = false
|
||||
castButton.setImage(UIImage(named: "CastConnected"), for: .normal)
|
||||
manager.currentCastSession?.start()
|
||||
|
||||
|
||||
jellyfinCastChannel!.delegate = self
|
||||
session.add(jellyfinCastChannel!)
|
||||
|
||||
|
||||
if let client = session.remoteMediaClient {
|
||||
client.add(self)
|
||||
}
|
||||
|
||||
|
||||
let playNowOptions: [String: Any] = [
|
||||
"items": [[
|
||||
"Id": self.manifest.id!,
|
||||
|
@ -878,33 +878,33 @@ extension PlayerViewController: GCKSessionManagerListener {
|
|||
]
|
||||
sendJellyfinCommand(command: "PlayNow", options: playNowOptions)
|
||||
}
|
||||
|
||||
|
||||
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKCastSession) {
|
||||
self.jellyfinCastChannel = GCKGenericChannel(namespace: "urn:x-cast:com.connectsdk")
|
||||
self.sessionDidStart(manager: sessionManager, didStart: session)
|
||||
}
|
||||
|
||||
|
||||
func sessionManager(_ sessionManager: GCKSessionManager, didResumeCastSession session: GCKCastSession) {
|
||||
self.jellyfinCastChannel = GCKGenericChannel(namespace: "urn:x-cast:com.connectsdk")
|
||||
self.sessionDidStart(manager: sessionManager, didStart: session)
|
||||
}
|
||||
|
||||
|
||||
func sessionManager(_ sessionManager: GCKSessionManager, didFailToStart session: GCKCastSession, withError error: Error) {
|
||||
LogManager.shared.log.error((error as NSError).debugDescription)
|
||||
}
|
||||
|
||||
|
||||
func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKCastSession, withError error: Error?) {
|
||||
if error != nil {
|
||||
LogManager.shared.log.error((error! as NSError).debugDescription)
|
||||
}
|
||||
|
||||
|
||||
playerDestination = .local
|
||||
videoContentView.isHidden = false
|
||||
remoteTimeUpdateTimer?.invalidate()
|
||||
castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||
startLocalPlaybackEngine(false)
|
||||
}
|
||||
|
||||
|
||||
func sessionManager(_ sessionManager: GCKSessionManager, didSuspend session: GCKCastSession, with reason: GCKConnectionSuspendReason) {
|
||||
playerDestination = .local
|
||||
videoContentView.isHidden = false
|
||||
|
@ -947,7 +947,7 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func mediaPlayerTimeChanged(_ aNotification: Notification!) {
|
||||
let time = mediaPlayer.position
|
||||
if abs(time-lastTime) > 0.00005 {
|
||||
|
@ -955,7 +955,7 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
seekSlider.setValue(mediaPlayer.position, animated: true)
|
||||
delegate?.hideLoadingView(self)
|
||||
|
||||
|
||||
if manifest.type == "Episode" && upNextViewModel.item != nil {
|
||||
if time > 0.96 {
|
||||
upNextView.isHidden = false
|
||||
|
@ -965,9 +965,9 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
self.jumpForwardButton.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
timeText.text = String(mediaPlayer.remainingTime.stringValue.dropFirst())
|
||||
|
||||
|
||||
if CACurrentMediaTime() - controlsAppearTime > 5 {
|
||||
self.smallNextUpView()
|
||||
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
|
||||
|
@ -980,7 +980,7 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
}
|
||||
lastTime = time
|
||||
}
|
||||
|
||||
|
||||
if CACurrentMediaTime() - lastProgressReportTime > 5 {
|
||||
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
|
||||
sendProgressReport(eventName: "timeupdate")
|
||||
|
@ -993,36 +993,36 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
||||
var item: BaseItemDto
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
|
||||
var loadBinding: Binding<Bool>
|
||||
var pBinding: Binding<Bool>
|
||||
|
||||
|
||||
class Coordinator: NSObject, PlayerViewControllerDelegate {
|
||||
let loadBinding: Binding<Bool>
|
||||
let pBinding: Binding<Bool>
|
||||
|
||||
|
||||
init(loadBinding: Binding<Bool>, pBinding: Binding<Bool>) {
|
||||
self.loadBinding = loadBinding
|
||||
self.pBinding = pBinding
|
||||
}
|
||||
|
||||
|
||||
func hideLoadingView(_ viewController: PlayerViewController) {
|
||||
self.loadBinding.wrappedValue = false
|
||||
}
|
||||
|
||||
|
||||
func showLoadingView(_ viewController: PlayerViewController) {
|
||||
self.loadBinding.wrappedValue = true
|
||||
}
|
||||
|
||||
|
||||
func exitPlayer(_ viewController: PlayerViewController) {
|
||||
self.pBinding.wrappedValue = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(loadBinding: self.loadBinding, pBinding: self.pBinding)
|
||||
}
|
||||
|
||||
|
||||
typealias UIViewControllerType = PlayerViewController
|
||||
func makeUIViewController(context: UIViewControllerRepresentableContext<VLCPlayerWithControls>) -> VLCPlayerWithControls.UIViewControllerType {
|
||||
let storyboard = UIStoryboard(name: "VideoPlayer", bundle: nil)
|
||||
|
@ -1031,7 +1031,7 @@ struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
|||
customViewController.delegate = context.coordinator
|
||||
return customViewController
|
||||
}
|
||||
|
||||
|
||||
func updateUIViewController(_ uiViewController: VLCPlayerWithControls.UIViewControllerType, context: UIViewControllerRepresentableContext<VLCPlayerWithControls>) {
|
||||
}
|
||||
}
|
||||
|
@ -1041,7 +1041,7 @@ extension PlayerViewController {
|
|||
func sendProgressReport(eventName: String) {
|
||||
if (eventName == "timeupdate" && mediaPlayer.state == .playing) || eventName != "timeupdate" {
|
||||
let progressInfo = PlaybackProgressInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: (mediaPlayer.state == .paused), isMuted: false, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), 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)
|
||||
.sink(receiveCompletion: { result in
|
||||
print(result)
|
||||
|
@ -1051,10 +1051,10 @@ extension PlayerViewController {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func sendStopReport() {
|
||||
let stopInfo = PlaybackStopInfo(item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), liveStreamId: nil, playSessionId: playSessionId, failed: nil, nextMediaType: nil, playlistItemId: "playlistItem0", nowPlayingQueue: [])
|
||||
|
||||
|
||||
PlaystateAPI.reportPlaybackStopped(playbackStopInfo: stopInfo)
|
||||
.sink(receiveCompletion: { result in
|
||||
print(result)
|
||||
|
@ -1063,14 +1063,14 @@ extension PlayerViewController {
|
|||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
|
||||
func sendPlayReport() {
|
||||
startTime = Int(Date().timeIntervalSince1970) * 10_000_000
|
||||
|
||||
|
||||
print("sending play report!")
|
||||
|
||||
|
||||
let startInfo = PlaybackStartInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: false, isMuted: false, positionTicks: manifest.userData?.playbackPositionTicks, playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
||||
|
||||
|
||||
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
||||
.sink(receiveCompletion: { result in
|
||||
print(result)
|
||||
|
|
Loading…
Reference in New Issue