Implement Factory (#587)
This commit is contained in:
parent
fb38394a43
commit
3ffb67a400
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import Nuke
|
import Nuke
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,6 +16,10 @@ import SwiftUI
|
||||||
import WidgetKit
|
import WidgetKit
|
||||||
|
|
||||||
final class MainCoordinator: NavigationCoordinatable {
|
final class MainCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
|
|
||||||
var stack: NavigationStack<MainCoordinator>
|
var stack: NavigationStack<MainCoordinator>
|
||||||
|
|
||||||
@Root
|
@Root
|
||||||
|
@ -60,13 +65,13 @@ final class MainCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func didSignIn() {
|
func didSignIn() {
|
||||||
LogManager.log.info("Received `didSignIn` from SwiftfinNotificationCenter.")
|
logger.info("Received `didSignIn` from SwiftfinNotificationCenter.")
|
||||||
root(\.mainTab)
|
root(\.mainTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func didSignOut() {
|
func didSignOut() {
|
||||||
LogManager.log.info("Received `didSignOut` from SwiftfinNotificationCenter.")
|
logger.info("Received `didSignOut` from SwiftfinNotificationCenter.")
|
||||||
root(\.serverList)
|
root(\.serverList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,17 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import Nuke
|
import Nuke
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class MainCoordinator: NavigationCoordinatable {
|
final class MainCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
|
|
||||||
var stack = NavigationStack<MainCoordinator>(initial: \MainCoordinator.mainTab)
|
var stack = NavigationStack<MainCoordinator>(initial: \MainCoordinator.mainTab)
|
||||||
|
|
||||||
@Root
|
@Root
|
||||||
|
@ -40,13 +45,13 @@ final class MainCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func didSignIn() {
|
func didSignIn() {
|
||||||
LogManager.log.info("Received `didSignIn` from NSNotificationCenter.")
|
logger.info("Received `didSignIn` from NSNotificationCenter.")
|
||||||
root(\.mainTab)
|
root(\.mainTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func didSignOut() {
|
func didSignOut() {
|
||||||
LogManager.log.info("Received `didSignOut` from NSNotificationCenter.")
|
logger.info("Received `didSignOut` from NSNotificationCenter.")
|
||||||
root(\.serverList)
|
root(\.serverList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import UIKit
|
||||||
extension BaseItemDto {
|
extension BaseItemDto {
|
||||||
func createVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> {
|
func createVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> {
|
||||||
|
|
||||||
LogManager.log.debug("Creating video player view model for item: \(id ?? "")")
|
LogManager.service().debug("Creating video player view model for item: \(id ?? "")")
|
||||||
|
|
||||||
let builder = DeviceProfileBuilder()
|
let builder = DeviceProfileBuilder()
|
||||||
// TODO: fix bitrate settings
|
// TODO: fix bitrate settings
|
||||||
|
@ -182,7 +182,7 @@ extension BaseItemDto {
|
||||||
|
|
||||||
func createLiveTVVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> {
|
func createLiveTVVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> {
|
||||||
|
|
||||||
LogManager.log.debug("Creating liveTV video player view model for item: \(id ?? "")")
|
LogManager.service().debug("Creating liveTV video player view model for item: \(id ?? "")")
|
||||||
|
|
||||||
let builder = DeviceProfileBuilder()
|
let builder = DeviceProfileBuilder()
|
||||||
// TODO: fix bitrate settings
|
// TODO: fix bitrate settings
|
||||||
|
|
|
@ -22,4 +22,8 @@ public extension URL {
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var documents: URL {
|
||||||
|
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,15 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import UDPBroadcast
|
import UDPBroadcast
|
||||||
|
|
||||||
public class ServerDiscovery {
|
public class ServerDiscovery {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
|
|
||||||
public struct ServerLookupResponse: Codable, Hashable, Identifiable {
|
public struct ServerLookupResponse: Codable, Hashable, Identifiable {
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
@ -56,7 +61,7 @@ public class ServerDiscovery {
|
||||||
func receiveHandler(_ ipAddress: String, _ port: Int, _ data: Data) {
|
func receiveHandler(_ ipAddress: String, _ port: Int, _ data: Data) {
|
||||||
do {
|
do {
|
||||||
let response = try JSONDecoder().decode(ServerLookupResponse.self, from: data)
|
let response = try JSONDecoder().decode(ServerLookupResponse.self, from: data)
|
||||||
LogManager.log.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery")
|
logger.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery")
|
||||||
completion(response)
|
completion(response)
|
||||||
} catch {
|
} catch {
|
||||||
completion(nil)
|
completion(nil)
|
||||||
|
@ -64,15 +69,15 @@ public class ServerDiscovery {
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorHandler(error: UDPBroadcastConnection.ConnectionError) {
|
func errorHandler(error: UDPBroadcastConnection.ConnectionError) {
|
||||||
LogManager.log.error("Error handling response: \(error.localizedDescription)", tag: "ServerDiscovery")
|
logger.error("Error handling response: \(error.localizedDescription)", tag: "ServerDiscovery")
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
self.connection = try! UDPBroadcastConnection(port: 7359, handler: receiveHandler, errorHandler: errorHandler)
|
self.connection = try! UDPBroadcastConnection(port: 7359, handler: receiveHandler, errorHandler: errorHandler)
|
||||||
try self.connection?.sendBroadcast("Who is JellyfinServer?")
|
try self.connection?.sendBroadcast("Who is JellyfinServer?")
|
||||||
LogManager.log.debug("Discovery broadcast sent", tag: "ServerDiscovery")
|
logger.debug("Discovery broadcast sent", tag: "ServerDiscovery")
|
||||||
} catch {
|
} catch {
|
||||||
LogManager.log.error("Error sending discovery broadcast", tag: "ServerDiscovery")
|
logger.error("Error sending discovery broadcast", tag: "ServerDiscovery")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,40 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import Puppy
|
import Puppy
|
||||||
|
|
||||||
class LogManager {
|
class LogManager {
|
||||||
|
|
||||||
static let log = Puppy()
|
static let service = Factory<Puppy>(scope: .singleton) {
|
||||||
|
Puppy.swiftfinInstance()
|
||||||
|
}
|
||||||
|
|
||||||
static func setup() {
|
// static let log = Puppy()
|
||||||
|
}
|
||||||
|
|
||||||
let logsDirectory = getDocumentsDirectory().appendingPathComponent("logs", isDirectory: true)
|
class LogFormatter: LogFormattable {
|
||||||
|
func formatMessage(
|
||||||
|
_ level: LogLevel,
|
||||||
|
message: String,
|
||||||
|
tag: String,
|
||||||
|
function: String,
|
||||||
|
file: String,
|
||||||
|
line: UInt,
|
||||||
|
swiftLogInfo: [String: String],
|
||||||
|
label: String,
|
||||||
|
date: Date,
|
||||||
|
threadID: UInt64
|
||||||
|
) -> String {
|
||||||
|
let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "")
|
||||||
|
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Puppy {
|
||||||
|
static func swiftfinInstance() -> Puppy {
|
||||||
|
let logsDirectory = URL.documents.appendingPathComponent("logs", isDirectory: true)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try FileManager.default.createDirectory(
|
try FileManager.default.createDirectory(
|
||||||
|
@ -38,33 +62,9 @@ class LogManager {
|
||||||
let consoleLogger = ConsoleLogger("org.jellyfin.swiftfin.logger.console")
|
let consoleLogger = ConsoleLogger("org.jellyfin.swiftfin.logger.console")
|
||||||
consoleLogger.format = LogFormatter()
|
consoleLogger.format = LogFormatter()
|
||||||
|
|
||||||
log.add(fileRotationLogger, withLevel: .debug)
|
let logger = Puppy()
|
||||||
log.add(consoleLogger, withLevel: .debug)
|
logger.add(fileRotationLogger, withLevel: .debug)
|
||||||
}
|
logger.add(consoleLogger, withLevel: .debug)
|
||||||
|
return logger
|
||||||
private static func getDocumentsDirectory() -> URL {
|
|
||||||
// find all possible documents directories for this user
|
|
||||||
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
|
||||||
|
|
||||||
// just send back the first one, which ought to be the only one
|
|
||||||
return paths[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogFormatter: LogFormattable {
|
|
||||||
func formatMessage(
|
|
||||||
_ level: LogLevel,
|
|
||||||
message: String,
|
|
||||||
tag: String,
|
|
||||||
function: String,
|
|
||||||
file: String,
|
|
||||||
line: UInt,
|
|
||||||
swiftLogInfo: [String: String],
|
|
||||||
label: String,
|
|
||||||
date: Date,
|
|
||||||
threadID: UInt64
|
|
||||||
) -> String {
|
|
||||||
let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "")
|
|
||||||
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,14 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class SwiftfinNotification {
|
class SwiftfinNotification {
|
||||||
|
|
||||||
|
@Injected(Notifications.service)
|
||||||
|
private var notificationService
|
||||||
|
|
||||||
private let notificationName: Notification.Name
|
private let notificationName: Notification.Name
|
||||||
|
|
||||||
fileprivate init(_ notificationName: Notification.Name) {
|
fileprivate init(_ notificationName: Notification.Name) {
|
||||||
|
@ -17,23 +21,21 @@ class SwiftfinNotification {
|
||||||
}
|
}
|
||||||
|
|
||||||
func post(object: Any? = nil) {
|
func post(object: Any? = nil) {
|
||||||
Notifications.main.post(name: notificationName, object: object)
|
notificationService.post(name: notificationName, object: object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe(_ observer: Any, selector: Selector) {
|
func subscribe(_ observer: Any, selector: Selector) {
|
||||||
Notifications.main.addObserver(observer, selector: selector, name: notificationName, object: nil)
|
notificationService.addObserver(observer, selector: selector, name: notificationName, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe(_ observer: Any) {
|
func unsubscribe(_ observer: Any) {
|
||||||
Notifications.main.removeObserver(self, name: notificationName, object: nil)
|
notificationService.removeObserver(self, name: notificationName, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Notifications {
|
enum Notifications {
|
||||||
|
|
||||||
static let main: NotificationCenter = {
|
static let service = Factory(scope: .singleton) { NotificationCenter() }
|
||||||
NotificationCenter()
|
|
||||||
}()
|
|
||||||
|
|
||||||
final class Key {
|
final class Key {
|
||||||
public typealias NotificationKey = Notifications.Key
|
public typealias NotificationKey = Notifications.Key
|
||||||
|
@ -50,10 +52,6 @@ enum Notifications {
|
||||||
static subscript(key: Key) -> SwiftfinNotification {
|
static subscript(key: Key) -> SwiftfinNotification {
|
||||||
key.underlyingNotification
|
key.underlyingNotification
|
||||||
}
|
}
|
||||||
|
|
||||||
static func unsubscribe(_ observer: Any) {
|
|
||||||
main.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notifications.Key {
|
extension Notifications.Key {
|
||||||
|
@ -63,8 +61,5 @@ extension Notifications.Key {
|
||||||
static let processDeepLink = NotificationKey("processDeepLink")
|
static let processDeepLink = NotificationKey("processDeepLink")
|
||||||
static let didPurge = NotificationKey("didPurge")
|
static let didPurge = NotificationKey("didPurge")
|
||||||
static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI")
|
static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI")
|
||||||
static let toggleOfflineMode = NotificationKey("toggleOfflineMode")
|
|
||||||
static let didDeleteOfflineItem = NotificationKey("didDeleteOfflineItem")
|
|
||||||
static let didAddDownload = NotificationKey("didAddDownload")
|
|
||||||
static let didSendStopReport = NotificationKey("didSendStopReport")
|
static let didSendStopReport = NotificationKey("didSendStopReport")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -49,7 +50,7 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
let uri = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
let uri = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
.trimmingCharacters(in: .objectReplacement)
|
.trimmingCharacters(in: .objectReplacement)
|
||||||
|
|
||||||
LogManager.log.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer")
|
logger.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer")
|
||||||
SessionManager.main.connectToServer(with: uri)
|
SessionManager.main.connectToServer(with: uri)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
@ -92,7 +93,7 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, receiveValue: { server in
|
}, receiveValue: { server in
|
||||||
LogManager.log.debug("Connected to server at \"\(uri)\"", tag: "connectToServer")
|
self.logger.debug("Connected to server at \"\(uri)\"", tag: "connectToServer")
|
||||||
self.router?.route(to: \.userSignIn, server)
|
self.router?.route(to: \.userSignIn, server)
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
|
@ -59,7 +59,7 @@ final class HomeViewModel: ViewModel {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func refresh() {
|
func refresh() {
|
||||||
LogManager.log.debug("Refresh called.")
|
logger.debug("Refresh called.")
|
||||||
|
|
||||||
refreshLibrariesLatest()
|
refreshLibrariesLatest()
|
||||||
refreshLatestAddedItems()
|
refreshLatestAddedItems()
|
||||||
|
@ -85,7 +85,7 @@ final class HomeViewModel: ViewModel {
|
||||||
var newLibraries: [BaseItemDto] = []
|
var newLibraries: [BaseItemDto] = []
|
||||||
|
|
||||||
response.items!.forEach { item in
|
response.items!.forEach { item in
|
||||||
LogManager.log
|
self.logger
|
||||||
.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
|
.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
|
||||||
if item.collectionType == "movies" || item.collectionType == "tvshows" {
|
if item.collectionType == "movies" || item.collectionType == "tvshows" {
|
||||||
newLibraries.append(item)
|
newLibraries.append(item)
|
||||||
|
@ -165,7 +165,7 @@ final class HomeViewModel: ViewModel {
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
}
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
LogManager.log.debug("Retrieved \(String(response.items!.count)) resume items")
|
self.logger.debug("Retrieved \(String(response.items!.count)) resume items")
|
||||||
|
|
||||||
self.resumeItems = response.items ?? []
|
self.resumeItems = response.items ?? []
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
@ -65,7 +66,7 @@ class ItemViewModel: ViewModel {
|
||||||
} else {
|
} else {
|
||||||
// Remove if necessary. Note that this cannot be in deinit as
|
// Remove if necessary. Note that this cannot be in deinit as
|
||||||
// holding as an observer won't allow the object to be deinit-ed
|
// holding as an observer won't allow the object to be deinit-ed
|
||||||
Notifications.unsubscribe(self)
|
Notifications[.didSendStopReport].unsubscribe(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestEpisodes() {
|
private func requestEpisodes() {
|
||||||
LogManager.log
|
logger
|
||||||
.debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))")
|
.debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))")
|
||||||
TvShowsAPI.getEpisodes(
|
TvShowsAPI.getEpisodes(
|
||||||
seriesId: item.seriesId ?? "",
|
seriesId: item.seriesId ?? "",
|
||||||
|
@ -78,7 +78,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager {
|
||||||
!episode.unaired && !episode.missing && episode.seasonId ?? "" == self.item.id!
|
!episode.unaired && !episode.missing && episode.seasonId ?? "" == self.item.id!
|
||||||
}) {
|
}) {
|
||||||
self.playButtonItem = nextUpItem
|
self.playButtonItem = nextUpItem
|
||||||
LogManager.log.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)")
|
self.logger.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if self.playButtonItem == nil && !self.episodes.isEmpty {
|
// if self.playButtonItem == nil && !self.episodes.isEmpty {
|
||||||
|
|
|
@ -49,7 +49,7 @@ final class SeriesItemViewModel: ItemViewModel, EpisodesRowManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getNextUp() {
|
private func getNextUp() {
|
||||||
LogManager.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
|
logger.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
|
||||||
TvShowsAPI.getNextUp(
|
TvShowsAPI.getNextUp(
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
seriesId: self.item.id!,
|
seriesId: self.item.id!,
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUICollection
|
import SwiftUICollection
|
||||||
|
@ -69,7 +70,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] _ in
|
}, receiveValue: { [weak self] _ in
|
||||||
LogManager.log.debug("Received Guide Info")
|
self?.logger.debug("Received Guide Info")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.getChannels()
|
self.getChannels()
|
||||||
})
|
})
|
||||||
|
@ -89,7 +90,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(response.items?.count ?? 0) Channels")
|
self?.logger.debug("Received \(response.items?.count ?? 0) Channels")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.channels = response.items ?? []
|
self.channels = response.items ?? []
|
||||||
self.getPrograms()
|
self.getPrograms()
|
||||||
|
@ -100,7 +101,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
private func getPrograms() {
|
private func getPrograms() {
|
||||||
// http://192.168.1.50:8096/LiveTv/Programs
|
// http://192.168.1.50:8096/LiveTv/Programs
|
||||||
guard !channels.isEmpty else {
|
guard !channels.isEmpty else {
|
||||||
LogManager.log.debug("Cannot get programs, channels list empty. ")
|
logger.debug("Cannot get programs, channels list empty. ")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let channelIds = channels.compactMap(\.id)
|
let channelIds = channels.compactMap(\.id)
|
||||||
|
@ -126,7 +127,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(response.items?.count ?? 0) Programs")
|
self?.logger.debug("Received \(response.items?.count ?? 0) Programs")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.programs = response.items ?? []
|
self.programs = response.items ?? []
|
||||||
self.channelPrograms = self.processChannelPrograms()
|
self.channelPrograms = self.processChannelPrograms()
|
||||||
|
@ -178,7 +179,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
timer = Timer(fire: nextMinute, interval: 60 * 10, repeats: true) { [weak self] _ in
|
timer = Timer(fire: nextMinute, interval: 60 * 10, repeats: true) { [weak self] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
LogManager.log.debug("LiveTVChannels schedule check...")
|
self.logger.debug("LiveTVChannels schedule check...")
|
||||||
DispatchQueue.global(qos: .background).async {
|
DispatchQueue.global(qos: .background).async {
|
||||||
let newChanPrgs = self.processChannelPrograms()
|
let newChanPrgs = self.processChannelPrograms()
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
|
@ -49,7 +49,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(response.items?.count ?? 0) Channels")
|
self?.logger.debug("Received \(response.items?.count ?? 0) Channels")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if let chans = response.items {
|
if let chans = response.items {
|
||||||
for chan in chans {
|
for chan in chans {
|
||||||
|
@ -82,7 +82,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs")
|
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.recommendedItems = response.items ?? []
|
self.recommendedItems = response.items ?? []
|
||||||
})
|
})
|
||||||
|
@ -109,7 +109,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Series Items")
|
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Series Items")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.seriesItems = response.items ?? []
|
self.seriesItems = response.items ?? []
|
||||||
})
|
})
|
||||||
|
@ -136,7 +136,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Movie Items")
|
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Movie Items")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.movieItems = response.items ?? []
|
self.movieItems = response.items ?? []
|
||||||
})
|
})
|
||||||
|
@ -159,7 +159,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Sports Items")
|
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Sports Items")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.sportsItems = response.items ?? []
|
self.sportsItems = response.items ?? []
|
||||||
})
|
})
|
||||||
|
@ -182,7 +182,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Kids Items")
|
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Kids Items")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.kidsItems = response.items ?? []
|
self.kidsItems = response.items ?? []
|
||||||
})
|
})
|
||||||
|
@ -205,7 +205,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) News Items")
|
self?.logger.debug("Received \(String(response.items?.count ?? 0)) News Items")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.newsItems = response.items ?? []
|
self.newsItems = response.items ?? []
|
||||||
})
|
})
|
||||||
|
|
|
@ -32,13 +32,13 @@ final class QuickConnectSettingsViewModel: ViewModel {
|
||||||
self.handleAPIRequestError(displayMessage: L10n.quickConnectInvalidError, completion: completion)
|
self.handleAPIRequestError(displayMessage: L10n.quickConnectInvalidError, completion: completion)
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure:
|
case .failure:
|
||||||
LogManager.log.debug("Invalid Quick Connect code entered")
|
self.logger.debug("Invalid Quick Connect code entered")
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
// receiving a successful HTTP response indicates a valid code
|
// receiving a successful HTTP response indicates a valid code
|
||||||
LogManager.log.debug("Valid Quick connect code entered")
|
self.logger.debug("Valid Quick connect code entered")
|
||||||
self.showSuccessMessage = true
|
self.showSuccessMessage = true
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
|
@ -24,6 +24,7 @@ final class SettingsViewModel: ViewModel {
|
||||||
|
|
||||||
self.server = server
|
self.server = server
|
||||||
self.user = user
|
self.user = user
|
||||||
|
super.init()
|
||||||
|
|
||||||
// Bitrates
|
// Bitrates
|
||||||
let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!
|
let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!
|
||||||
|
@ -33,10 +34,10 @@ final class SettingsViewModel: ViewModel {
|
||||||
do {
|
do {
|
||||||
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
|
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
|
||||||
} catch {
|
} catch {
|
||||||
LogManager.log.error("Error converting processed JSON into Swift compatible schema.")
|
logger.error("Error converting processed JSON into Swift compatible schema.")
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
LogManager.log.error("Error processing JSON file `bitrates.json`")
|
logger.error("Error processing JSON file `bitrates.json`")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track languages
|
// Track languages
|
||||||
|
|
|
@ -46,7 +46,7 @@ final class UserSignInViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func signIn(username: String, password: String) {
|
func signIn(username: String, password: String) {
|
||||||
LogManager.log.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login")
|
logger.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login")
|
||||||
|
|
||||||
let username = username.trimmingCharacters(in: .whitespacesAndNewlines)
|
let username = username.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
.trimmingCharacters(in: .objectReplacement)
|
.trimmingCharacters(in: .objectReplacement)
|
||||||
|
@ -99,7 +99,7 @@ final class UserSignInViewModel: ViewModel {
|
||||||
|
|
||||||
self.quickConnectSecret = response.secret
|
self.quickConnectSecret = response.secret
|
||||||
self.quickConnectCode = response.code
|
self.quickConnectCode = response.code
|
||||||
LogManager.log.debug("QuickConnect code: \(response.code ?? .emptyDash)")
|
self.logger.debug("QuickConnect code: \(response.code ?? .emptyDash)")
|
||||||
|
|
||||||
self.quickConnectTimer = RepeatingTimer(interval: 5) {
|
self.quickConnectTimer = RepeatingTimer(interval: 5) {
|
||||||
self.checkAuthStatus(onSuccess)
|
self.checkAuthStatus(onSuccess)
|
||||||
|
@ -120,7 +120,7 @@ final class UserSignInViewModel: ViewModel {
|
||||||
// this is a repeated call
|
// this is a repeated call
|
||||||
}, receiveValue: { value in
|
}, receiveValue: { value in
|
||||||
guard let authenticated = value.authenticated, authenticated else {
|
guard let authenticated = value.authenticated, authenticated else {
|
||||||
LogManager.log.debug("QuickConnect not authenticated yet")
|
self.logger.debug("QuickConnect not authenticated yet")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -511,7 +511,7 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { _ in
|
} receiveValue: { _ in
|
||||||
LogManager.log.debug("Start report sent for item: \(self.item.id ?? "No ID")")
|
self.logger.debug("Start report sent for item: \(self.item.id ?? "No ID")")
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
@ -547,7 +547,7 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { _ in
|
} receiveValue: { _ in
|
||||||
LogManager.log.debug("Pause report sent for item: \(self.item.id ?? "No ID")")
|
self.logger.debug("Pause report sent for item: \(self.item.id ?? "No ID")")
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
@ -592,7 +592,7 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { _ in
|
} receiveValue: { _ in
|
||||||
LogManager.log.debug("Playback progress sent for item: \(self.item.id ?? "No ID")")
|
self.logger.debug("Playback progress sent for item: \(self.item.id ?? "No ID")")
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
@ -619,7 +619,7 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { _ in
|
} receiveValue: { _ in
|
||||||
LogManager.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")")
|
self.logger.debug("Stop report sent for item: \(self.item.id ?? "No ID")")
|
||||||
Notifications[.didSendStopReport].post(object: self.item.id)
|
Notifications[.didSendStopReport].post(object: self.item.id)
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
|
@ -8,11 +8,14 @@
|
||||||
|
|
||||||
import ActivityIndicator
|
import ActivityIndicator
|
||||||
import Combine
|
import Combine
|
||||||
|
import Factory
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
class ViewModel: ObservableObject {
|
class ViewModel: ObservableObject {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
var logger
|
||||||
@Published
|
@Published
|
||||||
var isLoading = false
|
var isLoading = false
|
||||||
@Published
|
@Published
|
||||||
|
@ -39,18 +42,18 @@ class ViewModel: ObservableObject {
|
||||||
case .error(-1, _, _, _):
|
case .error(-1, _, _, _):
|
||||||
networkError = .URLError(response: errorResponse, displayMessage: displayMessage)
|
networkError = .URLError(response: errorResponse, displayMessage: displayMessage)
|
||||||
// Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented
|
// Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented
|
||||||
LogManager.log
|
logger
|
||||||
.error(
|
.error(
|
||||||
"Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)"
|
"Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)"
|
||||||
)
|
)
|
||||||
case .error(-2, _, _, _):
|
case .error(-2, _, _, _):
|
||||||
networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage)
|
networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage)
|
||||||
LogManager.log
|
logger
|
||||||
.error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)")
|
.error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)")
|
||||||
default:
|
default:
|
||||||
networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage)
|
networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage)
|
||||||
// Able to use user-facing friendly description here since just HTTP status codes
|
// Able to use user-facing friendly description here since just HTTP status codes
|
||||||
LogManager.log
|
logger
|
||||||
.error(
|
.error(
|
||||||
"Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.message)\n\(error.localizedDescription)"
|
"Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.message)\n\(error.localizedDescription)"
|
||||||
)
|
)
|
||||||
|
@ -66,7 +69,7 @@ class ViewModel: ObservableObject {
|
||||||
message: swiftfinError.errorDescription ?? ""
|
message: swiftfinError.errorDescription ?? ""
|
||||||
)
|
)
|
||||||
self.errorMessage = errorMessage
|
self.errorMessage = errorMessage
|
||||||
LogManager.log.error("Request failed: \(swiftfinError.errorDescription ?? "")")
|
logger.error("Request failed: \(swiftfinError.errorDescription ?? "")")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
let genericErrorMessage = ErrorMessage(
|
let genericErrorMessage = ErrorMessage(
|
||||||
|
@ -75,7 +78,7 @@ class ViewModel: ObservableObject {
|
||||||
message: error.localizedDescription
|
message: error.localizedDescription
|
||||||
)
|
)
|
||||||
self.errorMessage = genericErrorMessage
|
self.errorMessage = genericErrorMessage
|
||||||
LogManager.log.error("Request failed: Generic error - \(error.localizedDescription)")
|
logger.error("Request failed: Generic error - \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Factory
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct PlayButton: View {
|
struct PlayButton: View {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var itemRouter: ItemCoordinator.Router
|
private var itemRouter: ItemCoordinator.Router
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
|
@ -24,7 +27,7 @@ extension ItemView {
|
||||||
if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel {
|
if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel {
|
||||||
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.error("Attempted to play item but no playback information available")
|
logger.error("Attempted to play item but no playback information available")
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 15) {
|
HStack(spacing: 15) {
|
||||||
|
@ -55,7 +58,7 @@ extension ItemView {
|
||||||
selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
|
selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
|
||||||
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.error("Attempted to play item but no playback information available")
|
logger.error("Attempted to play item but no playback information available")
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label(L10n.playFromBeginning, systemImage: "gobackward")
|
Label(L10n.playFromBeginning, systemImage: "gobackward")
|
||||||
|
|
|
@ -10,6 +10,7 @@ import AVFoundation
|
||||||
import AVKit
|
import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import Factory
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -20,6 +21,9 @@ import UIKit
|
||||||
|
|
||||||
class LiveTVPlayerViewController: UIViewController {
|
class LiveTVPlayerViewController: UIViewController {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
|
|
||||||
// MARK: variables
|
// MARK: variables
|
||||||
|
|
||||||
private var viewModel: VideoPlayerViewModel
|
private var viewModel: VideoPlayerViewModel
|
||||||
|
@ -476,11 +480,11 @@ extension LiveTVPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import AVFoundation
|
||||||
import AVKit
|
import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import Factory
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -20,6 +21,9 @@ import UIKit
|
||||||
|
|
||||||
class VLCPlayerViewController: UIViewController {
|
class VLCPlayerViewController: UIViewController {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
|
|
||||||
// MARK: variables
|
// MARK: variables
|
||||||
|
|
||||||
private var viewModel: VideoPlayerViewModel
|
private var viewModel: VideoPlayerViewModel
|
||||||
|
@ -476,11 +480,11 @@ extension VLCPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -410,6 +410,8 @@
|
||||||
E19169CE272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; };
|
E19169CE272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; };
|
||||||
E19169CF272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; };
|
E19169CF272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; };
|
||||||
E192608028D28AAD002314B4 /* UserProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E192607F28D28AAD002314B4 /* UserProfileButton.swift */; };
|
E192608028D28AAD002314B4 /* UserProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E192607F28D28AAD002314B4 /* UserProfileButton.swift */; };
|
||||||
|
E192608328D2D0DB002314B4 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = E192608228D2D0DB002314B4 /* Factory */; };
|
||||||
|
E192608828D2E5F0002314B4 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = E192608728D2E5F0002314B4 /* Factory */; };
|
||||||
E1937A3B288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; };
|
E1937A3B288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; };
|
||||||
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; };
|
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; };
|
||||||
E1937A3E288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */; };
|
E1937A3E288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */; };
|
||||||
|
@ -1027,6 +1029,7 @@
|
||||||
E12186DE2718F1C50010884C /* Defaults in Frameworks */,
|
E12186DE2718F1C50010884C /* Defaults in Frameworks */,
|
||||||
E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */,
|
E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */,
|
||||||
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */,
|
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */,
|
||||||
|
E192608828D2E5F0002314B4 /* Factory in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1045,6 +1048,7 @@
|
||||||
62666E0327E5017100EC0ECD /* CoreMedia.framework in Frameworks */,
|
62666E0327E5017100EC0ECD /* CoreMedia.framework in Frameworks */,
|
||||||
62666E0627E5017A00EC0ECD /* CoreVideo.framework in Frameworks */,
|
62666E0627E5017A00EC0ECD /* CoreVideo.framework in Frameworks */,
|
||||||
E10EAA4D277BB716000269ED /* Sliders in Frameworks */,
|
E10EAA4D277BB716000269ED /* Sliders in Frameworks */,
|
||||||
|
E192608328D2D0DB002314B4 /* Factory in Frameworks */,
|
||||||
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
|
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
|
||||||
62666E0227E5016D00EC0ECD /* CoreGraphics.framework in Frameworks */,
|
62666E0227E5016D00EC0ECD /* CoreGraphics.framework in Frameworks */,
|
||||||
62666E1027E501B400EC0ECD /* VideoToolbox.framework in Frameworks */,
|
62666E1027E501B400EC0ECD /* VideoToolbox.framework in Frameworks */,
|
||||||
|
@ -2269,6 +2273,7 @@
|
||||||
E13AF3B928A0C598009093AB /* NukeUI */,
|
E13AF3B928A0C598009093AB /* NukeUI */,
|
||||||
E13AF3BB28A0C59E009093AB /* BlurHashKit */,
|
E13AF3BB28A0C59E009093AB /* BlurHashKit */,
|
||||||
E1734D7D28B9578100C66367 /* CollectionView */,
|
E1734D7D28B9578100C66367 /* CollectionView */,
|
||||||
|
E192608728D2E5F0002314B4 /* Factory */,
|
||||||
);
|
);
|
||||||
productName = "JellyfinPlayer tvOS";
|
productName = "JellyfinPlayer tvOS";
|
||||||
productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */;
|
productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */;
|
||||||
|
@ -2308,6 +2313,7 @@
|
||||||
E19E6E0628A0B958005C10C8 /* NukeUI */,
|
E19E6E0628A0B958005C10C8 /* NukeUI */,
|
||||||
E19E6E0928A0BEFF005C10C8 /* BlurHashKit */,
|
E19E6E0928A0BEFF005C10C8 /* BlurHashKit */,
|
||||||
E1734D7B28B9577700C66367 /* CollectionView */,
|
E1734D7B28B9577700C66367 /* CollectionView */,
|
||||||
|
E192608228D2D0DB002314B4 /* Factory */,
|
||||||
);
|
);
|
||||||
productName = JellyfinPlayer;
|
productName = JellyfinPlayer;
|
||||||
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
|
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
|
||||||
|
@ -2373,6 +2379,7 @@
|
||||||
E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */,
|
E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||||
E19E6E0828A0BEFF005C10C8 /* XCRemoteSwiftPackageReference "BlurHashKit" */,
|
E19E6E0828A0BEFF005C10C8 /* XCRemoteSwiftPackageReference "BlurHashKit" */,
|
||||||
E1734D7A28B9577700C66367 /* XCRemoteSwiftPackageReference "CollectionView" */,
|
E1734D7A28B9577700C66367 /* XCRemoteSwiftPackageReference "CollectionView" */,
|
||||||
|
E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -3447,6 +3454,14 @@
|
||||||
kind = branch;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/hmlongco/Factory";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 1.0.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/kean/Nuke";
|
repositoryURL = "https://github.com/kean/Nuke";
|
||||||
|
@ -3609,6 +3624,16 @@
|
||||||
package = E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
package = E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
||||||
productName = JellyfinAPI;
|
productName = JellyfinAPI;
|
||||||
};
|
};
|
||||||
|
E192608228D2D0DB002314B4 /* Factory */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */;
|
||||||
|
productName = Factory;
|
||||||
|
};
|
||||||
|
E192608728D2E5F0002314B4 /* Factory */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */;
|
||||||
|
productName = Factory;
|
||||||
|
};
|
||||||
E19E6E0428A0B958005C10C8 /* Nuke */ = {
|
E19E6E0428A0B958005C10C8 /* Nuke */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */;
|
package = E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||||
|
|
|
@ -54,6 +54,15 @@
|
||||||
"version" : "6.3.0"
|
"version" : "6.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "factory",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/hmlongco/Factory",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "8557426f3286e20b631ecdac8115242f888656e0",
|
||||||
|
"version" : "1.2.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "jellyfin-sdk-swift",
|
"identity" : "jellyfin-sdk-swift",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|
|
@ -20,7 +20,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
|
|
||||||
// Lazily initialize datastack
|
// Lazily initialize datastack
|
||||||
_ = SwiftfinStore.dataStack
|
_ = SwiftfinStore.dataStack
|
||||||
LogManager.setup()
|
|
||||||
|
|
||||||
let audioSession = AVAudioSession.sharedInstance()
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
do {
|
do {
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Factory
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct PlayButton: View {
|
struct PlayButton: View {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var itemRouter: ItemCoordinator.Router
|
private var itemRouter: ItemCoordinator.Router
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
|
@ -22,7 +25,7 @@ extension ItemView {
|
||||||
if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel {
|
if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel {
|
||||||
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.error("Attempted to play item but no playback information available")
|
logger.error("Attempted to play item but no playback information available")
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -47,7 +50,7 @@ extension ItemView {
|
||||||
selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
|
selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
|
||||||
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.error("Attempted to play item but no playback information available")
|
logger.error("Attempted to play item but no playback information available")
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label(L10n.playFromBeginning, systemImage: "gobackward")
|
Label(L10n.playFromBeginning, systemImage: "gobackward")
|
||||||
|
|
|
@ -10,6 +10,7 @@ import AVFoundation
|
||||||
import AVKit
|
import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import Factory
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
import MobileVLCKit
|
import MobileVLCKit
|
||||||
|
@ -19,6 +20,10 @@ import UIKit
|
||||||
// TODO: Look at making the VLC player layer a view
|
// TODO: Look at making the VLC player layer a view
|
||||||
|
|
||||||
class LiveTVPlayerViewController: UIViewController {
|
class LiveTVPlayerViewController: UIViewController {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
|
|
||||||
// MARK: variables
|
// MARK: variables
|
||||||
|
|
||||||
private var viewModel: VideoPlayerViewModel
|
private var viewModel: VideoPlayerViewModel
|
||||||
|
@ -532,11 +537,11 @@ extension LiveTVPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import AVFoundation
|
||||||
import AVKit
|
import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import Factory
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
import MobileVLCKit
|
import MobileVLCKit
|
||||||
|
@ -19,6 +20,10 @@ import UIKit
|
||||||
// TODO: Look at making the VLC player layer a view
|
// TODO: Look at making the VLC player layer a view
|
||||||
|
|
||||||
class VLCPlayerViewController: UIViewController {
|
class VLCPlayerViewController: UIViewController {
|
||||||
|
|
||||||
|
@Injected(LogManager.service)
|
||||||
|
private var logger
|
||||||
|
|
||||||
// MARK: variables
|
// MARK: variables
|
||||||
|
|
||||||
private var viewModel: VideoPlayerViewModel
|
private var viewModel: VideoPlayerViewModel
|
||||||
|
@ -615,11 +620,11 @@ extension VLCPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue